/*-------------------------------------------------------------------------
 *
 * Copyright (c) 2004-2008, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *   $PostgreSQL: pgjdbc/org/postgresql/jdbc2/AbstractJdbc2Statement.java,v 1.114 2009/05/27 23:55:19 jurka Exp $
 *
 *-------------------------------------------------------------------------
 */
package org.postgresql.jdbc2;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.*;
import java.sql.*;
import java.util.ArrayList;
import java.util.Vector;
import java.util.Calendar;

import org.postgresql.Driver;
import org.postgresql.largeobject.*;
import org.postgresql.core.*;
import org.postgresql.core.types.*;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;
import org.postgresql.util.PGobject;
import org.postgresql.util.GT;

/**
 * This class defines methods of the jdbc2 specification. The real Statement
 * class (for jdbc2) is org.postgresql.jdbc2.Jdbc2Statement
 */

//这个类把Statement, PreparedStatement, CallableStatement三者的方法都包含进来了
//CallableStatement中的所有setXXX(String parameterName..)方法和getXXX(String parameterName..)方法在这个类的子类中都没实现
//jdk1.4新增加的方法放在子类AbstractJdbc3Statement中(AbstractJdbc3Statement可以认为对应jdk1.4)
//jdk1.6新增加的方法放在子类AbstractJdbc4Statement中(AbstractJdbc4Statement可以认为对应jdk1.6)
//在jdk1.5中CallableStatement接口没加方法进来
//AbstractJdbc2Statement类只含jdk1.4之前的api实现
public abstract class AbstractJdbc2Statement implements BaseStatement {
	private static my.Debug DEBUG=new my.Debug(my.Debug.Statement);//我加上的

	protected ArrayList batchStatements = null;
	protected ArrayList batchParameters = null;
	protected final int resultsettype; // the resultset type to return
	// (ResultSet.TYPE_xxx)
	protected final int concurrency; // is it updateable or not?
	// (ResultSet.CONCUR_xxx)
	protected int fetchdirection = ResultSet.FETCH_FORWARD; // fetch direction
	// hint (currently
	// ignored)

	/**
	 * Does the caller of execute/executeUpdate want generated keys for this
	 * execution? This is set by Statement methods that have generated keys
	 * arguments and cleared after execution is complete.
	 */
	protected boolean wantsGeneratedKeysOnce = false;

	/**
	 * Was this PreparedStatement created to return generated keys for every
	 * execution? This is set at creation time and never cleared by execution.
	 */
	public boolean wantsGeneratedKeysAlways = false;

	// The connection who created us
	protected BaseConnection connection;

	/** The warnings chain. */
	protected SQLWarning warnings = null;
	/** The last warning of the warning chain. */
    protected SQLWarning lastWarning = null;

	/** Maximum number of rows to return, 0 = unlimited */
	protected int maxrows = 0;

	/** Number of rows to get in a batch. */
	protected int fetchSize = 0;

	/** Timeout (in seconds) for a query (not used) */
	protected int timeout = 0;

	protected boolean replaceProcessingEnabled = true;

	/** The current results. */
	protected ResultWrapper result = null;

	/** The first unclosed result. */
	protected ResultWrapper firstUnclosedResult = null;

	/** Results returned by a statement that wants generated keys. */
	protected ResultWrapper generatedKeys = null;

	/**
	 * used to differentiate between new function call logic and old function
	 * call logic will be set to true if the server is < 8.1 or if we are using
	 * v2 protocol There is an exception to this where we are using v3, and the
	 * call does not have an out parameter before the call
	 */
	protected boolean adjustIndex = false;

	/*
	 * Used to set adjustIndex above
	 */
	protected boolean outParmBeforeFunc = false;

	// Static variables for parsing SQL when replaceProcessing is true.
	private static final short IN_SQLCODE = 0;
	private static final short IN_STRING = 1;
	private static final short IN_IDENTIFIER = 6;
	private static final short BACKSLASH = 2;
	private static final short ESC_TIMEDATE = 3;
	private static final short ESC_FUNCTION = 4;
	private static final short ESC_OUTERJOIN = 5;
	private static final short ESC_ESCAPECHAR = 7;

	protected final Query preparedQuery; // Query fragments for prepared
	// statement.
	protected final ParameterList preparedParameters; // Parameter values for
	// prepared statement.
	protected Query lastSimpleQuery;

	protected int m_prepareThreshold; // Reuse threshold to enable use of
	// PREPARE
	protected int m_useCount = 0; // Number of times this statement has been
	// used

	// Used by the callablestatement style methods
	private boolean isFunction;
	// functionReturnType contains the user supplied value to check
	// testReturn contains a modified version to make it easier to
	// check the getXXX methods..
	private int[] functionReturnType;
	private int[] testReturn;
	// returnTypeSet is true when a proper call to registerOutParameter has been
	// made
	private boolean returnTypeSet;
	protected Object[] callResult;
	protected int maxfieldSize = 0;

	public ResultSet createDriverResultSet(Field[] fields, Vector tuples)
			throws SQLException {
		return createResultSet(null, fields, tuples, null);
	}

	//Statement的情形
	public AbstractJdbc2Statement(AbstractJdbc2Connection c, int rsType,
			int rsConcurrency) throws SQLException {
		try {//我加上的
		DEBUG.P(this,"AbstractJdbc2Statement(3)");

		this.connection = c;

		//这两个字段只用于PreparedStatement的情形
		this.preparedQuery = null;
		this.preparedParameters = null;

		this.lastSimpleQuery = null;
		resultsettype = rsType;
		concurrency = rsConcurrency;

		}finally{//我加上的
		DEBUG.P(0,this,"AbstractJdbc2Statement(3)");
		}
	}

	//PreparedStatement的情形
	public AbstractJdbc2Statement(AbstractJdbc2Connection connection,
			String sql, boolean isCallable, int rsType, int rsConcurrency)
			throws SQLException {
		try {//我加上的
		DEBUG.P(this,"AbstractJdbc2Statement(5)");
		DEBUG.P("sql="+sql);
		DEBUG.P("isCallable="+isCallable);

		this.connection = connection;
		this.lastSimpleQuery = null;

		String parsed_sql = replaceProcessing(sql);

		DEBUG.P("parsed_sql="+parsed_sql);
		if (isCallable)
			parsed_sql = modifyJdbcCall(parsed_sql);

		DEBUG.P("parsed_sql="+parsed_sql);

		this.preparedQuery = connection.getQueryExecutor()
				.createParameterizedQuery(parsed_sql);
		this.preparedParameters = preparedQuery.createParameterList();

		int inParamCount = preparedParameters.getInParameterCount() + 1;
		
		//如果是conn.prepareCall("{ ? = call upper( ? ) }");
		//inParamCount=3
		DEBUG.P("inParamCount="+inParamCount);

		this.testReturn = new int[inParamCount];
		this.functionReturnType = new int[inParamCount];

		resultsettype = rsType;
		concurrency = rsConcurrency;

		}finally{//我加上的
		DEBUG.P(0,this,"AbstractJdbc2Statement(5)");
		}
	}

	//不同的jdbc版本返回不同的ResultSet
	public abstract ResultSet createResultSet(Query originalQuery,
			Field[] fields, Vector tuples, ResultCursor cursor)
			throws SQLException;

	public BaseConnection getPGConnection() {
		return connection;
	}

	//实际上是与setCursorName对应，
	//只不过java.sql.Statement及其子接口中都没有定义getCursorName，
	//setCursorName也不做任何事
	public String getFetchingCursorName() {
		return null;
	}

	public int getFetchSize() {
		return fetchSize; //默认是0
	}

	protected boolean wantsScrollableResultSet() {
		return resultsettype != ResultSet.TYPE_FORWARD_ONLY;
	}

	//子类AbstractJdbc3Statement有覆盖此方法
	//rsHoldability == ResultSet.HOLD_CURSORS_OVER_COMMIT
	protected boolean wantsHoldableResultSet() {
		return false;
	}

	//
	// ResultHandler implementations for updates, queries, and either-or.
	//

	public class StatementResultHandler implements ResultHandler {
		private SQLException error;

		//ResultWrapper可以代表两种情况：
		//只有结果集，或者只有updateCount、insertOID(相当于自动生成的id)
		//不能同时包含这两种情况
		private ResultWrapper results;

		ResultWrapper getResults() {
			return results;
		}

		private void append(ResultWrapper newResult) {
			if (results == null)
				results = newResult;
			else
				results.append(newResult);
		}

		public void handleResultRows(Query fromQuery, Field[] fields,
				Vector tuples, ResultCursor cursor) {
			try {
				ResultSet rs = AbstractJdbc2Statement.this.createResultSet(
						fromQuery, fields, tuples, cursor);
				append(new ResultWrapper(rs));
			} catch (SQLException e) {
				handleError(e);
			}
		}

		public void handleCommandStatus(String status, int updateCount,
				long insertOID) {
			append(new ResultWrapper(updateCount, insertOID));
		}

		public void handleWarning(SQLWarning warning) {
			AbstractJdbc2Statement.this.addWarning(warning);
		}

		public void handleError(SQLException newError) {
			if (error == null)
				error = newError;
			else
				error.setNextException(newError);
		}

		public void handleCompletion() throws SQLException {
			if (error != null)
				throw error;
		}
	}

	/*
	 * Execute a SQL statement that retruns a single ResultSet
	 * 
	 * @param sql typically a static SQL SELECT statement
	 * 
	 * @return a ResulSet that contains the data produced by the query
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public java.sql.ResultSet executeQuery(String p_sql) throws SQLException {
		try {//我加上的
		DEBUG.P(this,"executeQuery(1)");

		if (preparedQuery != null)
			throw new PSQLException(
					GT
							.tr("Can''t use query methods that take a query string on a PreparedStatement."),
					PSQLState.WRONG_OBJECT_TYPE);

		//同execute，如果是select，返回true，否则返回false
		if (!executeWithFlags(p_sql, 0))
			throw new PSQLException(GT
					.tr("No results were returned by the query."),
					PSQLState.NO_DATA);

		//一条p_sql只能得到一个结果集
		if (result.getNext() != null)
			throw new PSQLException(GT
					.tr("Multiple ResultSets were returned by the query."),
					PSQLState.TOO_MANY_RESULTS);

		return (ResultSet) result.getResultSet();

		}finally{//我加上的
		DEBUG.P(0,this,"executeQuery(1)");
		}
	}

	/*
	 * A Prepared SQL query is executed and its ResultSet is returned
	 * 
	 * @return a ResultSet that contains the data produced by the * query -
	 * never null
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public java.sql.ResultSet executeQuery() throws SQLException {
		//内部使用的是preparedQuery，
		//其他同上面的executeQuery(String p_sql)
		if (!executeWithFlags(0))
			throw new PSQLException(GT
					.tr("No results were returned by the query."),
					PSQLState.NO_DATA);

		if (result.getNext() != null)
			throw new PSQLException(GT
					.tr("Multiple ResultSets were returned by the query."),
					PSQLState.TOO_MANY_RESULTS);

		return (ResultSet) result.getResultSet();
	}

	/*
	 * Execute a SQL INSERT, UPDATE or DELETE statement. In addition SQL
	 * statements that return nothing such as SQL DDL statements can be executed
	 * 
	 * @param sql a SQL statement
	 * 
	 * @return either a row count, or 0 for SQL commands
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public int executeUpdate(String p_sql) throws SQLException {
		try {//我加上的
		DEBUG.P(this,"executeUpdate(1)");
		DEBUG.P("p_sql="+p_sql);
		if (preparedQuery != null)
			throw new PSQLException(
					GT
							.tr("Can''t use query methods that take a query string on a PreparedStatement."),
					PSQLState.WRONG_OBJECT_TYPE); //SQLState="42809"，42开头的是语法类型的错语
		
		DEBUG.P("isFunction="+isFunction);
		if (isFunction) {
			//因为一个函数可能内部会包含select语句，所以不能指定QUERY_NO_RESULTS
			executeWithFlags(p_sql, 0);
			//函数只返回0
			return 0;
		}

        executeWithFlags(p_sql, QueryExecutor.QUERY_NO_RESULTS);

        ResultWrapper iter = result;
        while (iter != null) {
            if (iter.getResultSet() != null) {
                throw new PSQLException(GT.tr("A result was returned when none was expected."),
                    				  PSQLState.TOO_MANY_RESULTS);

            }
            iter = iter.getNext();
        }

        return getUpdateCount();

		}finally{//我加上的
		DEBUG.P(0,this,"executeUpdate(1)");
		}
	}

	/*
	 * Execute a SQL INSERT, UPDATE or DELETE statement. In addition, SQL
	 * statements that return nothing such as SQL DDL statements can be
	 * executed.
	 * 
	 * @return either the row count for INSERT, UPDATE or DELETE; or * 0 for SQL
	 * statements that return nothing.
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public int executeUpdate() throws SQLException
    {
        if( isFunction )
        {
            executeWithFlags(0);
            return 0;
        }

        executeWithFlags(QueryExecutor.QUERY_NO_RESULTS);

        ResultWrapper iter = result;
        while (iter != null) {
            if (iter.getResultSet() != null) {
                throw new PSQLException(GT.tr("A result was returned when none was expected."),
                    				  PSQLState.TOO_MANY_RESULTS);

            }
            iter = iter.getNext();
        }

        return getUpdateCount();
    }

	/*
	 * Execute a SQL statement that may return multiple results. We don't have
	 * to worry about this since we do not support multiple ResultSets. You can
	 * use getResultSet or getUpdateCount to retrieve the result.
	 * 
	 * @param sql any SQL statement
	 * 
	 * @return true if the next result is a ResulSet, false if it is an update
	 * count or there are no more results
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public boolean execute(String p_sql) throws SQLException {
		if (preparedQuery != null)
			throw new PSQLException(
					GT
							.tr("Can''t use query methods that take a query string on a PreparedStatement."),
					PSQLState.WRONG_OBJECT_TYPE);

		return executeWithFlags(p_sql, 0);
	}

	//如果执行sql后得到一个结果集返回true，否则返回false
	public boolean executeWithFlags(String p_sql, int flags)
			throws SQLException {
		try {//我加上的
		DEBUG.P(this,"executeWithFlags(2)");

		checkClosed();
		p_sql = replaceProcessing(p_sql);
		
		DEBUG.P("p_sql="+p_sql);

		Query simpleQuery = connection.getQueryExecutor().createSimpleQuery(
				p_sql);
		execute(simpleQuery, null, QueryExecutor.QUERY_ONESHOT | flags);
		this.lastSimpleQuery = simpleQuery;

		DEBUG.P("result="+result);

		return (result != null && result.getResultSet() != null);

		}finally{//我加上的
		DEBUG.P(0,this,"executeWithFlags(2)");
		}
	}

	public boolean execute() throws SQLException {
		try {//我加上的
		DEBUG.P(this,"execute()");

		return executeWithFlags(0);

		}finally{//我加上的
		DEBUG.P(0,this,"execute()");
		}
	}

	public boolean executeWithFlags(int flags) throws SQLException {
		try {//我加上的
		DEBUG.P(this,"executeWithFlags(1)");

		checkClosed();

		execute(preparedQuery, preparedParameters, flags);

		// If we are executing and there are out parameters
		// callable statement function set the return data

		DEBUG.P("isFunction="+isFunction);
		DEBUG.P("returnTypeSet="+returnTypeSet);
		if (isFunction && returnTypeSet) {
			if (result == null || result.getResultSet() == null)
				throw new PSQLException(
						GT
								.tr("A CallableStatement was executed with nothing returned."),
						PSQLState.NO_DATA);

			ResultSet rs = result.getResultSet();
			if (!rs.next())
				throw new PSQLException(
						GT
								.tr("A CallableStatement was executed with nothing returned."),
						PSQLState.NO_DATA);

			// figure out how many columns
			int cols = rs.getMetaData().getColumnCount();

			int outParameterCount = preparedParameters.getOutParameterCount();

			DEBUG.P("cols="+cols);
			DEBUG.P("outParameterCount="+outParameterCount);

			if (cols != outParameterCount)
				throw new PSQLException(
						GT
								.tr("A CallableStatement was executed with an invalid number of parameters"),
						PSQLState.SYNTAX_ERROR);

			// reset last result fetched (for wasNull)
			lastIndex = 0;

			// allocate enough space for all possible parameters without regard
			// to in/out
			callResult = new Object[preparedParameters.getParameterCount() + 1];

			DEBUG.PA("functionReturnType",functionReturnType);

			// move them into the result set
			for (int i = 0, j = 0; i < cols; i++, j++) {
				// find the next out parameter, the assumption is that the
				// functionReturnType
				// array will be initialized with 0 and only out parameters will
				// have values
				// other than 0. 0 is the value for java.sql.Types.NULL, which
				// should not
				// conflict
				while (j < functionReturnType.length
						&& functionReturnType[j] == 0)
					j++;

				callResult[j] = rs.getObject(i + 1);
				int columnType = rs.getMetaData().getColumnType(i + 1);

				if (columnType != functionReturnType[j]) {
					// this is here for the sole purpose of passing the cts
					if (columnType == Types.DOUBLE
							&& functionReturnType[j] == Types.REAL) {
						// return it as a float
						if (callResult[j] != null)
							callResult[j] = new Float(((Double) callResult[j])
									.floatValue());
					} else {
						throw new PSQLException(
								GT
										.tr(
												"A CallableStatement function was executed and the out parameter {0} was of type {1} however type {2} was registered.",
												new Object[] {
														new Integer(i + 1),
														"java.sql.Types="
																+ columnType,
														"java.sql.Types="
																+ functionReturnType[j] }),
								PSQLState.DATA_TYPE_MISMATCH);
					}
				}

			}
			rs.close();
			result = null;
			return false;
		}

		return (result != null && result.getResultSet() != null);

		}finally{//我加上的
		DEBUG.P(0,this,"executeWithFlags(1)");
		}
	}

	protected void execute(Query queryToExecute, ParameterList queryParameters,
			int flags) throws SQLException {
		try {//我加上的
		DEBUG.P(this,"execute(3)");
		DEBUG.P("queryToExecute="+queryToExecute);
		DEBUG.P("queryParameters="+queryParameters);
		DEBUG.P("flags="+flags);

		// Every statement execution clears any previous warnings.
		clearWarnings();

		DEBUG.P("firstUnclosedResult="+firstUnclosedResult);

		// Close any existing resultsets associated with this statement.
		while (firstUnclosedResult != null) {
			if (firstUnclosedResult.getResultSet() != null)
				firstUnclosedResult.getResultSet().close();
			firstUnclosedResult = firstUnclosedResult.getNext();
		}

		DEBUG.P("lastSimpleQuery="+lastSimpleQuery);

		if (lastSimpleQuery != null) {
			lastSimpleQuery.close();
			lastSimpleQuery = null;
		}

		// Enable cursor-based resultset if possible.
		if (fetchSize > 0 && !wantsScrollableResultSet()
				&& !connection.getAutoCommit() && !wantsHoldableResultSet())
			flags |= QueryExecutor.QUERY_FORWARD_CURSOR;

		if (wantsGeneratedKeysOnce || wantsGeneratedKeysAlways) {
			flags |= QueryExecutor.QUERY_BOTH_ROWS_AND_STATUS;

			// If the no results flag is set (from executeUpdate)
			// clear it so we get the generated keys results.
			//

			//如果要得到GeneratedKeys，那么必须得到结果集，所以要取消原来的QUERY_NO_RESULTS
			if ((flags & QueryExecutor.QUERY_NO_RESULTS) != 0)
				flags &= ~(QueryExecutor.QUERY_NO_RESULTS);
		}

		DEBUG.P("m_prepareThreshold="+m_prepareThreshold);

		// Only use named statements after we hit the threshold
		if (preparedQuery != null) {
			++m_useCount; // We used this statement once more.
			if (m_prepareThreshold == 0 || m_useCount < m_prepareThreshold)
				flags |= QueryExecutor.QUERY_ONESHOT;
		}

		DEBUG.P("m_useCount="+m_useCount);

		DEBUG.P("wantsGeneratedKeysOnce="+wantsGeneratedKeysOnce);
		DEBUG.P("wantsGeneratedKeysAlways="+wantsGeneratedKeysAlways);

		if (connection.getAutoCommit())
			flags |= QueryExecutor.QUERY_SUPPRESS_BEGIN;

		StatementResultHandler handler = new StatementResultHandler();
		result = null;
		connection.getQueryExecutor().execute(queryToExecute, queryParameters,
				handler, maxrows, fetchSize, flags);
		result = firstUnclosedResult = handler.getResults();

		//wantsGeneratedKeysOnce是Statement每次执行execute、executeUpdate时传送的
		//wantsGeneratedKeysAlways是调用Connection.prepareStatement构造PreparedStatement时
		//设置的，当执行PreparedStatement的execute、executeUpdate时都要保留GeneratedKeys.
		if (wantsGeneratedKeysOnce || wantsGeneratedKeysAlways) {
			generatedKeys = result;
			result = result.getNext();

			if (wantsGeneratedKeysOnce)
				wantsGeneratedKeysOnce = false;
		}

		}finally{//我加上的
		DEBUG.P(0,this,"execute(3)");
		}
	}

	/*
	 * setCursorName defines the SQL cursor name that will be used by subsequent
	 * execute methods. This name can then be used in SQL positioned
	 * update/delete statements to identify the current row in the ResultSet
	 * generated by this statement. If a database doesn't support positioned
	 * update/delete, this method is a no-op.
	 * 
	 * <p><B>Note:</B> By definition, positioned update/delete execution must be
	 * done by a different Statement than the one which generated the ResultSet
	 * being used for positioning. Also, cursor names must be unique within a
	 * Connection.
	 * 
	 * @param name the new cursor name
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public void setCursorName(String name) throws SQLException {
		checkClosed();
		// No-op.
	}

	protected boolean isClosed = false;
	private int lastIndex = 0;

	/*
	 * getUpdateCount returns the current result as an update count, if the
	 * result is a ResultSet or there are no more results, -1 is returned. It
	 * should only be called once per result.
	 * 
	 * @return the current result as an update count.
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public int getUpdateCount() throws SQLException {
		checkClosed();
		if (result == null || result.getResultSet() != null)
			return -1;

		return result.getUpdateCount();
	}

	/*
	 * getMoreResults moves to a Statement's next result. If it returns true,
	 * this result is a ResulSet.
	 * 
	 * @return true if the next ResultSet is valid
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public boolean getMoreResults() throws SQLException {
		if (result == null)
			return false;

		result = result.getNext();

		// Close preceding resultsets.
		while (firstUnclosedResult != result) {
			if (firstUnclosedResult.getResultSet() != null)
				firstUnclosedResult.getResultSet().close();
			firstUnclosedResult = firstUnclosedResult.getNext();
		}

		return (result != null && result.getResultSet() != null);
	}

	/*
	 * The maxRows limit is set to limit the number of rows that any ResultSet
	 * can contain. If the limit is exceeded, the excess rows are silently
	 * dropped.
	 * 
	 * @return the current maximum row limit; zero means unlimited
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public int getMaxRows() throws SQLException {
		checkClosed();
		return maxrows;
	}

	/*
	 * Set the maximum number of rows
	 * 
	 * @param max the new max rows limit; zero means unlimited
	 * 
	 * @exception SQLException if a database access error occurs
	 * 
	 * @see getMaxRows
	 */
	public void setMaxRows(int max) throws SQLException {
		checkClosed();
		if (max < 0)
			throw new PSQLException(
					GT
							.tr("Maximum number of rows must be a value grater than or equal to 0."),
					PSQLState.INVALID_PARAMETER_VALUE);
		maxrows = max;
	}

	/*
	 * If escape scanning is on (the default), the driver will do escape
	 * substitution before sending the SQL to the database.
	 * 
	 * @param enable true to enable; false to disable
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public void setEscapeProcessing(boolean enable) throws SQLException {
		checkClosed();
		replaceProcessingEnabled = enable;
	}

	/*
	 * The queryTimeout limit is the number of seconds the driver will wait for
	 * a Statement to execute. If the limit is exceeded, a SQLException is
	 * thrown.
	 * 
	 * @return the current query timeout limit in seconds; 0 = unlimited
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public int getQueryTimeout() throws SQLException {
		checkClosed();
		return timeout;
	}

	/*
	 * Sets the queryTimeout limit
	 * 
	 * @param seconds - the new query timeout limit in seconds
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public void setQueryTimeout(int seconds) throws SQLException {
		checkClosed();
		if (seconds < 0)
			throw new PSQLException(
					GT
							.tr("Query timeout must be a value greater than or equals to 0."),
					PSQLState.INVALID_PARAMETER_VALUE);

		if (seconds > 0)
			throw Driver
					.notImplemented(this.getClass(), "setQueryTimeout(int)");

		timeout = seconds;
	}

	/**
     * This adds a warning to the warning chain.  We track the
     * tail of the warning chain as well to avoid O(N) behavior
     * for adding a new warning to an existing chain.  Some
     * server functions which RAISE NOTICE (or equivalent) produce
     * a ton of warnings.
     * @param warn warning to add
     */
    public void addWarning(SQLWarning warn)
    {
        if (warnings == null) {
            warnings = warn;
            lastWarning = warn;
        } else {
            lastWarning.setNextWarning(warn);
            lastWarning = warn;
        }
    }

	/*
	 * The first warning reported by calls on this Statement is returned. A
	 * Statement's execute methods clear its SQLWarning chain. Subsequent
	 * Statement warnings will be chained to this SQLWarning.
	 * 
	 * <p>The Warning chain is automatically cleared each time a statement is
	 * (re)executed.
	 * 
	 * <p><B>Note:</B> If you are processing a ResultSet then any warnings
	 * associated with ResultSet reads will be chained on the ResultSet object.
	 * 
	 * @return the first SQLWarning on null
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public SQLWarning getWarnings() throws SQLException {
		checkClosed();
		return warnings;
	}

	/*
	 * The maxFieldSize limit (in bytes) is the maximum amount of data returned
	 * for any column value; it only applies to BINARY, VARBINARY,
	 * LONGVARBINARY, CHAR, VARCHAR and LONGVARCHAR columns. If the limit is
	 * exceeded, the excess data is silently discarded.
	 * 
	 * @return the current max column size limit; zero means unlimited
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public int getMaxFieldSize() throws SQLException {
		return maxfieldSize;
	}

	/*
	 * Sets the maxFieldSize
	 * 
	 * @param max the new max column size limit; zero means unlimited
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public void setMaxFieldSize(int max) throws SQLException {
		checkClosed();
		if (max < 0)
			throw new PSQLException(
					GT
							.tr("The maximum field size must be a value greater than or equal to 0."),
					PSQLState.INVALID_PARAMETER_VALUE);
		maxfieldSize = max;
	}

	/*
	 * After this call, getWarnings returns null until a new warning is reported
	 * for this Statement.
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public void clearWarnings() throws SQLException {
		warnings = null;
		lastWarning = null;
	}

	/*
	 * getResultSet returns the current result as a ResultSet. It should only be
	 * called once per result.
	 * 
	 * @return the current result set; null if there are no more
	 * 
	 * @exception SQLException if a database access error occurs (why?)
	 */
	public java.sql.ResultSet getResultSet() throws SQLException {
		checkClosed();

		if (result == null)
			return null;

		return (ResultSet) result.getResultSet();
	}

	/*
	 * In many cases, it is desirable to immediately release a Statement's
	 * database and JDBC resources instead of waiting for this to happen when it
	 * is automatically closed. The close method provides this immediate
	 * release.
	 * 
	 * <p><B>Note:</B> A Statement is automatically closed when it is garbage
	 * collected. When a Statement is closed, its current ResultSet, if one
	 * exists, is also closed.
	 * 
	 * @exception SQLException if a database access error occurs (why?)
	 */
	public void close() throws SQLException {
		// closing an already closed Statement is a no-op.
		if (isClosed)
			return;

		// Force the ResultSet(s) to close
		while (firstUnclosedResult != null) {
			if (firstUnclosedResult.getResultSet() != null)
				firstUnclosedResult.getResultSet().close();
			firstUnclosedResult = firstUnclosedResult.getNext();
		}

		if (lastSimpleQuery != null)
			lastSimpleQuery.close();

		if (preparedQuery != null)
			preparedQuery.close();

		// Disasociate it from us
		result = firstUnclosedResult = null;
		isClosed = true;
	}

	/**
	 * This finalizer ensures that statements that have allocated server-side
	 * resources free them when they become unreferenced.
	 */
	protected void finalize() {
		try {
			close();
		} catch (SQLException e) {
		}
	}

	/*
	 * Filter the SQL string of Java SQL Escape clauses.
	 * 
	 * Currently implemented Escape clauses are those mentioned in 11.3 in the
	 * specification. Basically we look through the sql string for {d xxx}, {t
	 * xxx}, {ts xxx}, {oj xxx} or {fn xxx} in non-string sql code. When we find
	 * them, we just strip the escape part leaving only the xxx part. So,
	 * something like "select * from x where d={d '2001-10-09'}" would return
	 * "select * from x where d= '2001-10-09'".
	 */
	protected String replaceProcessing(String p_sql) throws SQLException {
		try {//我加上的
		DEBUG.P(this,"replaceProcessing(1)");
		DEBUG.P("replaceProcessingEnabled="+replaceProcessingEnabled);

		if (replaceProcessingEnabled) {
			// Since escape codes can only appear in SQL CODE, we keep track
			// of if we enter a string or not.
			int len = p_sql.length();
			StringBuffer newsql = new StringBuffer(len);
			int i = 0;
			while (i < len) {
				i = parseSql(p_sql, i, newsql, false, connection
						.getStandardConformingStrings());
				// We need to loop here in case we encounter invalid
				// SQL, consider: SELECT a FROM t WHERE (1 > 0)) ORDER BY a
				// We can't ending replacing after the extra closing paren
				// because that changes a syntax error to a valid query
				// that isn't what the user specified.
				if (i < len) {
					newsql.append(p_sql.charAt(i));
					i++;
				}
			}
			return newsql.toString();
		} else {
			return p_sql;
		}

		}finally{//我加上的
		DEBUG.P(0,this,"replaceProcessing(1)");
		}
	}

	/**
	 * parse the given sql from index i, appending it to the gven buffer until
	 * we hit an unmatched right parentheses or end of string. When the
	 * stopOnComma flag is set we also stop processing when a comma is found in
	 * sql text that isn't inside nested parenthesis.
	 * 
	 * @param p_sql
	 *            the original query text
	 * @param i
	 *            starting position for replacing
	 * @param newsql
	 *            where to write the replaced output
	 * @param stopOnComma
	 *            should we stop after hitting the first comma in sql text?
	 * @param stdStrings
	 *            whether standard_conforming_strings is on
	 * @return the position we stopped processing at
	 */
	protected static int parseSql(String p_sql, int i, StringBuffer newsql,
			boolean stopOnComma, boolean stdStrings) throws SQLException {
		try {//我加上的
		DEBUG.P(AbstractJdbc2Statement.class,"parseSql(5)");
		DEBUG.P("p_sql="+p_sql);
		DEBUG.P("i="+i);
		DEBUG.P("newsql="+newsql);
		DEBUG.P("stopOnComma="+stopOnComma);
		DEBUG.P("stdStrings="+stdStrings);

		short state = IN_SQLCODE;
		int len = p_sql.length();
		int nestedParenthesis = 0;
		boolean endOfNested = false;

		// because of the ++i loop
		i--;
		while (!endOfNested && ++i < len) {
			char c = p_sql.charAt(i);
			switch (state) {
			case IN_SQLCODE:
				if (c == '\'') // start of a string?
					state = IN_STRING;
				else if (c == '"') // start of a identifer?
					state = IN_IDENTIFIER;
				else if (c == '(') { // begin nested sql
					nestedParenthesis++;
				} else if (c == ')') { // end of nested sql
					nestedParenthesis--;
					if (nestedParenthesis < 0) {
						endOfNested = true;
						break;
					}
				} else if (stopOnComma && c == ',' && nestedParenthesis == 0) {
					endOfNested = true;
					break;
				} else if (c == '{') { // start of an escape code?
					if (i + 1 < len) {
						char next = p_sql.charAt(i + 1);
						char nextnext = (i + 2 < len) ? p_sql.charAt(i + 2)
								: '\0';
						if (next == 'd' || next == 'D') {
							state = ESC_TIMEDATE;
							i++;
							newsql.append("DATE ");
							break;
						} else if (next == 't' || next == 'T') {
							state = ESC_TIMEDATE;
							if (nextnext == 's' || nextnext == 'S') {
								// timestamp constant
								i += 2;
								newsql.append("TIMESTAMP ");
							} else {
								// time constant
								i++;
								newsql.append("TIME ");
							}
							break;
						} else if (next == 'f' || next == 'F') {
							state = ESC_FUNCTION;
							i += (nextnext == 'n' || nextnext == 'N') ? 2 : 1;
							break;
						} else if (next == 'o' || next == 'O') {
							state = ESC_OUTERJOIN;
							i += (nextnext == 'j' || nextnext == 'J') ? 2 : 1;
							break;
						} else if (next == 'e' || next == 'E') { // we assume
							// that
							// escape is
							// the only
							// escape
							// sequence
							// beginning
							// with e
							state = ESC_ESCAPECHAR;
							break;
						}
					}
				}
				newsql.append(c);
				break;

			case IN_STRING:
				if (c == '\'') // end of string?
					state = IN_SQLCODE;
				else if (c == '\\' && !stdStrings) // a backslash?
					state = BACKSLASH;

				newsql.append(c);
				break;

			case IN_IDENTIFIER:
				if (c == '"') // end of identifier
					state = IN_SQLCODE;
				newsql.append(c);
				break;

			case BACKSLASH:
				state = IN_STRING;

				newsql.append(c);
				break;

			case ESC_FUNCTION:
				// extract function name
				String functionName;
				int posArgs = p_sql.indexOf('(', i);
				if (posArgs != -1) {
					functionName = p_sql.substring(i, posArgs).trim();
					// extract arguments
					i = posArgs + 1;// we start the scan after the first (
					StringBuffer args = new StringBuffer();
					i = parseSql(p_sql, i, args, false, stdStrings);
					// translate the function and parse arguments
					newsql.append(escapeFunction(functionName, args.toString(),
							stdStrings));
				}
				// go to the end of the function copying anything found
				i++;
				while (i < len && p_sql.charAt(i) != '}')
					newsql.append(p_sql.charAt(i++));
				state = IN_SQLCODE; // end of escaped function (or query)
				break;
			case ESC_TIMEDATE:
			case ESC_OUTERJOIN:
			case ESC_ESCAPECHAR:
				if (c == '}')
					state = IN_SQLCODE; // end of escape code.
				else
					newsql.append(c);
				break;
			} // end switch
		}
		return i;

		}finally{//我加上的
		DEBUG.P(0,AbstractJdbc2Statement.class,"parseSql(5)");
		}
	}

	/**
	 * generate sql for escaped functions
	 * 
	 * @param functionName
	 *            the escaped function name
	 * @param args
	 *            the arguments for this functin
	 * @param stdStrings
	 *            whether standard_conforming_strings is on
	 * @return the right postgreSql sql
	 */
	protected static String escapeFunction(String functionName, String args,
			boolean stdStrings) throws SQLException {
		// parse function arguments
		int len = args.length();
		int i = 0;
		ArrayList parsedArgs = new ArrayList();
		while (i < len) {
			StringBuffer arg = new StringBuffer();
			int lastPos = i;
			i = parseSql(args, i, arg, true, stdStrings);
			if (lastPos != i) {
				parsedArgs.add(arg);
			}
			i++;
		}
		// we can now tranlate escape functions
		try {
			Method escapeMethod = EscapedFunctions.getFunction(functionName);
			return (String) escapeMethod.invoke(null,
					new Object[] { parsedArgs });
		} catch (InvocationTargetException e) {
			if (e.getTargetException() instanceof SQLException)
				throw (SQLException) e.getTargetException();
			else
				throw new PSQLException(e.getTargetException().getMessage(),
						PSQLState.SYSTEM_ERROR);
		} catch (Exception e) {
			// by default the function name is kept unchanged
			StringBuffer buf = new StringBuffer();
			buf.append(functionName).append('(');
			for (int iArg = 0; iArg < parsedArgs.size(); iArg++) {
				buf.append(parsedArgs.get(iArg));
				if (iArg != (parsedArgs.size() - 1))
					buf.append(',');
			}
			buf.append(')');
			return buf.toString();
		}
	}

	/*
	 * 
	 * The following methods are postgres extensions and are defined in the
	 * interface BaseStatement
	 */

	/*
	 * Returns the Last inserted/updated oid. Deprecated in 7.2 because range of
	 * OID values is greater than a java signed int.
	 * 
	 * @deprecated Replaced by getLastOID in 7.2
	 */
	public int getInsertedOID() throws SQLException {
		checkClosed();
		if (result == null)
			return 0;
		return (int) result.getInsertOID();
	}

	/*
	 * Returns the Last inserted/updated oid.
	 * 
	 * @return OID of last insert
	 * 
	 * @since 7.2
	 */
	public long getLastOID() throws SQLException {
		checkClosed();
		if (result == null)
			return 0;
		return result.getInsertOID(); //上面的getInsertedOID()返回的是int，这里是long
	}

	/*
	 * Set a parameter to SQL NULL
	 * 
	 * <p><B>Note:</B> You must specify the parameter's SQL type.
	 * 
	 * @param parameterIndex the first parameter is 1, etc...
	 * 
	 * @param sqlType the SQL type code defined in java.sql.Types
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public void setNull(int parameterIndex, int sqlType) throws SQLException {
		checkClosed();

		int oid;
		switch (sqlType) {
		case Types.INTEGER:
			oid = Oid.INT4;
			break;
		case Types.TINYINT:
		case Types.SMALLINT:
			oid = Oid.INT2;
			break;
		case Types.BIGINT:
			oid = Oid.INT8;
			break;
		case Types.REAL:
			oid = Oid.FLOAT4;
			break;
		case Types.DOUBLE:
		case Types.FLOAT:
			oid = Oid.FLOAT8;
			break;
		case Types.DECIMAL:
		case Types.NUMERIC:
			oid = Oid.NUMERIC;
			break;
		case Types.CHAR:
			oid = Oid.BPCHAR;
			break;
		case Types.VARCHAR:
		case Types.LONGVARCHAR:
			oid = Oid.VARCHAR;
			break;
		case Types.DATE:
			oid = Oid.DATE;
			break;
		/*case Types.TIME:
			oid = Oid.TIME;
			break;
		case Types.TIMESTAMP:
			oid = Oid.TIMESTAMPTZ;
			break;*/
		case Types.TIME:
        case Types.TIMESTAMP:
            oid = Oid.UNSPECIFIED;
            break;
		case Types.BIT:
			oid = Oid.BOOL;
			break;
		case Types.BINARY:
		case Types.VARBINARY:
		case Types.LONGVARBINARY:
			if (connection.haveMinimumCompatibleVersion("7.2")) {
				oid = Oid.BYTEA;
			} else {
				oid = Oid.OID;
			}
			break;
		case Types.BLOB:
		case Types.CLOB:
			oid = Oid.OID;
			break;
		case Types.ARRAY:
		case Types.DISTINCT:
		case Types.STRUCT:
		case Types.NULL:
		case Types.OTHER:
			oid = Oid.UNSPECIFIED;
			break;
		default:
			// Bad Types value.
			throw new PSQLException(GT.tr("Unknown Types value."),
					PSQLState.INVALID_PARAMETER_TYPE);
		}
		if (adjustIndex)
			parameterIndex--;
		preparedParameters.setNull(parameterIndex, oid);
	}

	/*
	 * Set a parameter to a Java boolean value. The driver converts this to a
	 * SQL BIT value when it sends it to the database.
	 * 
	 * @param parameterIndex the first parameter is 1...
	 * 
	 * @param x the parameter value
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public void setBoolean(int parameterIndex, boolean x) throws SQLException {
		checkClosed();
		bindString(parameterIndex, x ? "1" : "0", Oid.BOOL);
	}

	/*
	 * Set a parameter to a Java byte value. The driver converts this to a SQL
	 * TINYINT value when it sends it to the database.
	 * 
	 * @param parameterIndex the first parameter is 1...
	 * 
	 * @param x the parameter value
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public void setByte(int parameterIndex, byte x) throws SQLException {
		checkClosed();
		bindLiteral(parameterIndex, Integer.toString(x), Oid.INT2);
	}

	/*
	 * Set a parameter to a Java short value. The driver converts this to a SQL
	 * SMALLINT value when it sends it to the database.
	 * 
	 * @param parameterIndex the first parameter is 1...
	 * 
	 * @param x the parameter value
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public void setShort(int parameterIndex, short x) throws SQLException {
		checkClosed();
		bindLiteral(parameterIndex, Integer.toString(x), Oid.INT2);
	}

	/*
	 * Set a parameter to a Java int value. The driver converts this to a SQL
	 * INTEGER value when it sends it to the database.
	 * 
	 * @param parameterIndex the first parameter is 1...
	 * 
	 * @param x the parameter value
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public void setInt(int parameterIndex, int x) throws SQLException {
		checkClosed();
		bindLiteral(parameterIndex, Integer.toString(x), Oid.INT4);
	}

	/*
	 * Set a parameter to a Java long value. The driver converts this to a SQL
	 * BIGINT value when it sends it to the database.
	 * 
	 * @param parameterIndex the first parameter is 1...
	 * 
	 * @param x the parameter value
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public void setLong(int parameterIndex, long x) throws SQLException {
		checkClosed();
		bindLiteral(parameterIndex, Long.toString(x), Oid.INT8);
	}

	/*
	 * Set a parameter to a Java float value. The driver converts this to a SQL
	 * FLOAT value when it sends it to the database.
	 * 
	 * @param parameterIndex the first parameter is 1...
	 * 
	 * @param x the parameter value
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public void setFloat(int parameterIndex, float x) throws SQLException {
		checkClosed();
		bindLiteral(parameterIndex, Float.toString(x), Oid.FLOAT8);
	}

	/*
	 * Set a parameter to a Java double value. The driver converts this to a SQL
	 * DOUBLE value when it sends it to the database
	 * 
	 * @param parameterIndex the first parameter is 1...
	 * 
	 * @param x the parameter value
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public void setDouble(int parameterIndex, double x) throws SQLException {
		checkClosed();
		bindLiteral(parameterIndex, Double.toString(x), Oid.FLOAT8);
	}

	/*
	 * Set a parameter to a java.lang.BigDecimal value. The driver converts this
	 * to a SQL NUMERIC value when it sends it to the database.
	 * 
	 * @param parameterIndex the first parameter is 1...
	 * 
	 * @param x the parameter value
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public void setBigDecimal(int parameterIndex, BigDecimal x)
			throws SQLException {
		checkClosed();
		if (x == null)
			setNull(parameterIndex, Types.DECIMAL);
		else
			bindLiteral(parameterIndex, x.toString(), Oid.NUMERIC);
	}

	/*
	 * Set a parameter to a Java String value. The driver converts this to a SQL
	 * VARCHAR or LONGVARCHAR value (depending on the arguments size relative to
	 * the driver's limits on VARCHARs) when it sends it to the database.
	 * 
	 * @param parameterIndex the first parameter is 1...
	 * 
	 * @param x the parameter value
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public void setString(int parameterIndex, String x) throws SQLException {
		checkClosed();
		setString(parameterIndex, x,
				(connection.getStringVarcharFlag() ? Oid.VARCHAR
						: Oid.UNSPECIFIED));
	}

	protected void setString(int parameterIndex, String x, int oid)
			throws SQLException {
		// if the passed string is null, then set this column to null
		checkClosed();
		if (x == null) {
			if (adjustIndex)
				parameterIndex--;
			preparedParameters.setNull(parameterIndex, oid);
		} else
			bindString(parameterIndex, x, oid);
	}

	/*
	 * Set a parameter to a Java array of bytes. The driver converts this to a
	 * SQL VARBINARY or LONGVARBINARY (depending on the argument's size relative
	 * to the driver's limits on VARBINARYs) when it sends it to the database.
	 * 
	 * <p>Implementation note: <br>With org.postgresql, this creates a large
	 * object, and stores the objects oid in this column.
	 * 
	 * @param parameterIndex the first parameter is 1...
	 * 
	 * @param x the parameter value
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public void setBytes(int parameterIndex, byte[] x) throws SQLException {
		checkClosed();

		if (null == x) {
			setNull(parameterIndex, Types.VARBINARY);
			return;
		}

		if (connection.haveMinimumCompatibleVersion("7.2")) {
			// Version 7.2 supports the bytea datatype for byte arrays
			byte[] copy = new byte[x.length];
			System.arraycopy(x, 0, copy, 0, x.length);
			preparedParameters.setBytea(parameterIndex, copy, 0, x.length);
		} else {
			// Version 7.1 and earlier support done as LargeObjects
			LargeObjectManager lom = connection.getLargeObjectAPI();
			long oid = lom.createLO();
			LargeObject lob = lom.open(oid);
			lob.write(x);
			lob.close();
			setLong(parameterIndex, oid);
		}
	}

	/*
	 * Set a parameter to a java.sql.Date value. The driver converts this to a
	 * SQL DATE value when it sends it to the database.
	 * 
	 * @param parameterIndex the first parameter is 1...
	 * 
	 * @param x the parameter value
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public void setDate(int parameterIndex, java.sql.Date x)
			throws SQLException {
		setDate(parameterIndex, x, null);
	}

	/*
	 * Set a parameter to a java.sql.Time value. The driver converts this to a
	 * SQL TIME value when it sends it to the database.
	 * 
	 * @param parameterIndex the first parameter is 1...));
	 * 
	 * @param x the parameter value
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public void setTime(int parameterIndex, Time x) throws SQLException {
		setTime(parameterIndex, x, null);
	}

	/*
	 * Set a parameter to a java.sql.Timestamp value. The driver converts this
	 * to a SQL TIMESTAMP value when it sends it to the database.
	 * 
	 * @param parameterIndex the first parameter is 1...
	 * 
	 * @param x the parameter value
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public void setTimestamp(int parameterIndex, Timestamp x)
			throws SQLException {
		setTimestamp(parameterIndex, x, null);
	}

	private void setCharacterStreamPost71(int parameterIndex, InputStream x,
			int length, String encoding) throws SQLException {

		if (x == null) {
			setNull(parameterIndex, Types.VARCHAR);
			return;
		}
		if (length < 0)
			throw new PSQLException(GT.tr("Invalid stream length {0}.",
					new Integer(length)), PSQLState.INVALID_PARAMETER_VALUE);

		// Version 7.2 supports AsciiStream for all PG text types (char,
		// varchar, text)
		// As the spec/javadoc for this method indicate this is to be used for
		// large String values (i.e. LONGVARCHAR) PG doesn't have a separate
		// long varchar datatype, but with toast all text datatypes are capable
		// of
		// handling very large values. Thus the implementation ends up calling
		// setString() since there is no current way to stream the value to the
		// server
		try {
			InputStreamReader l_inStream = new InputStreamReader(x, encoding);
			char[] l_chars = new char[length];
			int l_charsRead = 0;
			while (true) {
				int n = l_inStream.read(l_chars, l_charsRead, length
						- l_charsRead);
				if (n == -1)
					break;

				l_charsRead += n;

				if (l_charsRead == length)
					break;
			}

			setString(parameterIndex, new String(l_chars, 0, l_charsRead),
					Oid.VARCHAR);
		} catch (UnsupportedEncodingException l_uee) {
			throw new PSQLException(GT
					.tr("The JVM claims not to support the {0} encoding.",
							encoding), PSQLState.UNEXPECTED_ERROR, l_uee);
		} catch (IOException l_ioe) {
			throw new PSQLException(GT.tr("Provided InputStream failed."),
					PSQLState.UNEXPECTED_ERROR, l_ioe);
		}
	}

	/*
	 * When a very large ASCII value is input to a LONGVARCHAR parameter, it may
	 * be more practical to send it via a java.io.InputStream. JDBC will read
	 * the data from the stream as needed, until it reaches end-of-file. The
	 * JDBC driver will do any necessary conversion from ASCII to the database
	 * char format.
	 * 
	 * <P><B>Note:</B> This stream object can either be a standard Java stream
	 * object or your own subclass that implements the standard interface.
	 * 
	 * @param parameterIndex the first parameter is 1...
	 * 
	 * @param x the parameter value
	 * 
	 * @param length the number of bytes in the stream
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public void setAsciiStream(int parameterIndex, InputStream x, int length)
			throws SQLException {
		checkClosed();
		if (connection.haveMinimumCompatibleVersion("7.2")) {
			setCharacterStreamPost71(parameterIndex, x, length, "ASCII");
		} else {
			// Version 7.1 supported only LargeObjects by treating everything
			// as binary data
			setBinaryStream(parameterIndex, x, length);
		}
	}

	/*
	 * When a very large Unicode value is input to a LONGVARCHAR parameter, it
	 * may be more practical to send it via a java.io.InputStream. JDBC will
	 * read the data from the stream as needed, until it reaches end-of-file.
	 * The JDBC driver will do any necessary conversion from UNICODE to the
	 * database char format.
	 * 
	 * <P><B>Note:</B> This stream object can either be a standard Java stream
	 * object or your own subclass that implements the standard interface.
	 * 
	 * @param parameterIndex the first parameter is 1...
	 * 
	 * @param x the parameter value
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public void setUnicodeStream(int parameterIndex, InputStream x, int length)
			throws SQLException {
		checkClosed();
		if (connection.haveMinimumCompatibleVersion("7.2")) {
			setCharacterStreamPost71(parameterIndex, x, length, "UTF-8");
		} else {
			// Version 7.1 supported only LargeObjects by treating everything
			// as binary data
			setBinaryStream(parameterIndex, x, length);
		}
	}

	/*
	 * When a very large binary value is input to a LONGVARBINARY parameter, it
	 * may be more practical to send it via a java.io.InputStream. JDBC will
	 * read the data from the stream as needed, until it reaches end-of-file.
	 * 
	 * <P><B>Note:</B> This stream object can either be a standard Java stream
	 * object or your own subclass that implements the standard interface.
	 * 
	 * @param parameterIndex the first parameter is 1...
	 * 
	 * @param x the parameter value
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public void setBinaryStream(int parameterIndex, InputStream x, int length)
			throws SQLException {
		checkClosed();

		if (x == null) {
			setNull(parameterIndex, Types.VARBINARY);
			return;
		}

		if (length < 0)
			throw new PSQLException(GT.tr("Invalid stream length {0}.",
					new Integer(length)), PSQLState.INVALID_PARAMETER_VALUE);

		if (connection.haveMinimumCompatibleVersion("7.2")) {
			// Version 7.2 supports BinaryStream for for the PG bytea type
			// As the spec/javadoc for this method indicate this is to be used
			// for
			// large binary values (i.e. LONGVARBINARY) PG doesn't have a
			// separate
			// long binary datatype, but with toast the bytea datatype is
			// capable of
			// handling very large values.

			preparedParameters.setBytea(parameterIndex, x, length);
		} else {
			// Version 7.1 only supported streams for LargeObjects
			// but the jdbc spec indicates that streams should be
			// available for LONGVARBINARY instead
			LargeObjectManager lom = connection.getLargeObjectAPI();
			long oid = lom.createLO();
			LargeObject lob = lom.open(oid);
			OutputStream los = lob.getOutputStream();
			try {
				// could be buffered, but then the OutputStream returned by
				// LargeObject
				// is buffered internally anyhow, so there would be no
				// performance
				// boost gained, if anything it would be worse!
				int c = x.read();
				int p = 0;
				while (c > -1 && p < length) {
					los.write(c);
					c = x.read();
					p++;
				}
				los.close();
			} catch (IOException se) {
				throw new PSQLException(GT.tr("Provided InputStream failed."),
						PSQLState.UNEXPECTED_ERROR, se);
			}
			// lob is closed by the stream so don't call lob.close()
			setLong(parameterIndex, oid);
		}
	}

	/*
	 * In general, parameter values remain in force for repeated used of a
	 * Statement. Setting a parameter value automatically clears its previous
	 * value. However, in coms cases, it is useful to immediately release the
	 * resources used by the current parameter values; this can be done by
	 * calling clearParameters
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public void clearParameters() throws SQLException {
		preparedParameters.clear();
	}

	private PGType createInternalType(Object x, int targetType)
			throws PSQLException {
		if (x instanceof Byte)
			return PGByte.castToServerType((Byte) x, targetType);
		if (x instanceof Short)
			return PGShort.castToServerType((Short) x, targetType);
		if (x instanceof Integer)
			return PGInteger.castToServerType((Integer) x, targetType);
		if (x instanceof Long)
			return PGLong.castToServerType((Long) x, targetType);
		if (x instanceof Double)
			return PGDouble.castToServerType((Double) x, targetType);
		if (x instanceof Float)
			return PGFloat.castToServerType((Float) x, targetType);
		if (x instanceof BigDecimal)
			return PGBigDecimal.castToServerType((BigDecimal) x, targetType);
		// since all of the above are instances of Number make sure this is
		// after them
		if (x instanceof Number)
			return PGNumber.castToServerType((Number) x, targetType);
		if (x instanceof Boolean)
			return PGBoolean.castToServerType((Boolean) x, targetType);
		return new PGUnknown(x);

	}

	// Helper method for setting parameters to PGobject subclasses.
	private void setPGobject(int parameterIndex, PGobject x)
			throws SQLException {
		String typename = x.getType();
		int oid = connection.getTypeInfo().getPGType(typename);
		if (oid == Oid.UNSPECIFIED)
			throw new PSQLException(GT.tr("Unknown type {0}.", typename),
					PSQLState.INVALID_PARAMETER_TYPE);

		setString(parameterIndex, x.getValue(), oid);
	}

	/*
	 * Set the value of a parameter using an object; use the java.lang
	 * equivalent objects for integral values.
	 * 
	 * <P>The given Java object will be converted to the targetSqlType before
	 * being sent to the database.
	 * 
	 * <P>note that this method may be used to pass database-specific abstract
	 * data types. This is done by using a Driver-specific Java type and using a
	 * targetSqlType of java.sql.Types.OTHER
	 * 
	 * @param parameterIndex the first parameter is 1...
	 * 
	 * @param x the object containing the input parameter value
	 * 
	 * @param targetSqlType The SQL type to be send to the database
	 * 
	 * @param scale For java.sql.Types.DECIMAL or java.sql.Types.NUMERIC * types
	 * this is the number of digits after the decimal. For * all other types
	 * this value will be ignored.
	 * 
	 * @exception SQLException if a database access error occurs
	 */
	public void setObject(int parameterIndex, Object in, int targetSqlType,
			int scale) throws SQLException {
		checkClosed();

		if (in == null) {
			setNull(parameterIndex, targetSqlType);
			return;
		}

		Object pgType = createInternalType(in, targetSqlType);
		switch (targetSqlType) {
		case Types.INTEGER:
			bindLiteral(parameterIndex, pgType.toString(), Oid.INT4);
			break;
		case Types.TINYINT:
		case Types.SMALLINT:
			bindLiteral(parameterIndex, pgType.toString(), Oid.INT2);
			break;
		case Types.BIGINT:
			bindLiteral(parameterIndex, pgType.toString(), Oid.INT8);
			break;
		case Types.REAL:
			// TODO: is this really necessary ?
			// bindLiteral(parameterIndex, new
			// Float(pgType.toString()).toString(), Oid.FLOAT4);
			bindLiteral(parameterIndex, pgType.toString(), Oid.FLOAT4);
			break;
		case Types.DOUBLE:
		case Types.FLOAT:
			bindLiteral(parameterIndex, pgType.toString(), Oid.FLOAT8);
			break;
		case Types.DECIMAL:
		case Types.NUMERIC:
			bindLiteral(parameterIndex, pgType.toString(), Oid.NUMERIC);
			break;
		case Types.CHAR:
			setString(parameterIndex, pgType.toString(), Oid.BPCHAR);
			break;
		case Types.VARCHAR:
		case Types.LONGVARCHAR:
			setString(parameterIndex, pgType.toString(), Oid.VARCHAR);
			break;
		case Types.DATE:
			if (in instanceof java.sql.Date)
				setDate(parameterIndex, (java.sql.Date) in);
			else {
				java.sql.Date tmpd;
				if (in instanceof java.util.Date) {
					tmpd = new java.sql.Date(((java.util.Date) in).getTime());
				} else {
					tmpd = connection.getTimestampUtils().toDate(null,
							in.toString());
				}
				setDate(parameterIndex, tmpd);
			}
			break;
		case Types.TIME:
			if (in instanceof java.sql.Time)
				setTime(parameterIndex, (java.sql.Time) in);
			else {
				java.sql.Time tmpt;
				if (in instanceof java.util.Date) {
					tmpt = new java.sql.Time(((java.util.Date) in).getTime());
				} else {
					tmpt = connection.getTimestampUtils().toTime(null,
							in.toString());
				}
				setTime(parameterIndex, tmpt);
			}
			break;
		case Types.TIMESTAMP:
			if (in instanceof java.sql.Timestamp)
				setTimestamp(parameterIndex, (java.sql.Timestamp) in);
			else {
				java.sql.Timestamp tmpts;
				if (in instanceof java.util.Date) {
					tmpts = new java.sql.Timestamp(((java.util.Date) in)
							.getTime());
				} else {
					tmpts = connection.getTimestampUtils().toTimestamp(null,
							in.toString());
				}
				setTimestamp(parameterIndex, tmpts);
			}
			break;
		case Types.BIT:
			bindLiteral(parameterIndex, pgType.toString(), Oid.BOOL);
			break;
		case Types.BINARY:
		case Types.VARBINARY:
		case Types.LONGVARBINARY:
			setObject(parameterIndex, in);
			break;
		case Types.BLOB:
			if (in instanceof Blob)
				setBlob(parameterIndex, (Blob) in);
			else
				throw new PSQLException(
						GT.tr("Cannot cast an instance of {0} to type {1}",
								new Object[] { in.getClass().getName(),
										"Types.BLOB" }),
						PSQLState.INVALID_PARAMETER_TYPE);
			break;
		case Types.CLOB:
			if (in instanceof Clob)
				setClob(parameterIndex, (Clob) in);
			else
				throw new PSQLException(
						GT.tr("Cannot cast an instance of {0} to type {1}",
								new Object[] { in.getClass().getName(),
										"Types.CLOB" }),
						PSQLState.INVALID_PARAMETER_TYPE);
			break;
		case Types.ARRAY:
			if (in instanceof Array)
				setArray(parameterIndex, (Array) in);
			else
				throw new PSQLException(GT
						.tr("Cannot cast an instance of {0} to type {1}",
								new Object[] { in.getClass().getName(),
										"Types.ARRAY" }),
						PSQLState.INVALID_PARAMETER_TYPE);
			break;
		case Types.OTHER:
			if (in instanceof PGobject)
				setPGobject(parameterIndex, (PGobject) in);
			else
				bindString(parameterIndex, in.toString(), Oid.UNSPECIFIED);
			break;
		default:
			throw new PSQLException(GT.tr("Unsupported Types value: {0}",
					new Integer(targetSqlType)),
					PSQLState.INVALID_PARAMETER_TYPE);
		}
	}

	public void setObject(int parameterIndex, Object x, int targetSqlType)
			throws SQLException {
		setObject(parameterIndex, x, targetSqlType, 0);
	}

	/*
	 * This stores an Object into a parameter.
	 */
	public void setObject(int parameterIndex, Object x) throws SQLException {
		checkClosed();
		if (x == null)
			setNull(parameterIndex, Types.OTHER);
		else if (x instanceof String)
			setString(parameterIndex, (String) x);
		else if (x instanceof BigDecimal)
			setBigDecimal(parameterIndex, (BigDecimal) x);
		else if (x instanceof Short)
			setShort(parameterIndex, ((Short) x).shortValue());
		else if (x instanceof Integer)
			setInt(parameterIndex, ((Integer) x).intValue());
		else if (x instanceof Long)
			setLong(parameterIndex, ((Long) x).longValue());
		else if (x instanceof Float)
			setFloat(parameterIndex, ((Float) x).floatValue());
		else if (x instanceof Double)
			setDouble(parameterIndex, ((Double) x).doubleValue());
		else if (x instanceof byte[])
			setBytes(parameterIndex, (byte[]) x);
		else if (x instanceof java.sql.Date)
			setDate(parameterIndex, (java.sql.Date) x);
		else if (x instanceof Time)
			setTime(parameterIndex, (Time) x);
		else if (x instanceof Timestamp)
			setTimestamp(parameterIndex, (Timestamp) x);
		else if (x instanceof Boolean)
			setBoolean(parameterIndex, ((Boolean) x).booleanValue());
		else if (x instanceof Byte)
			setByte(parameterIndex, ((Byte) x).byteValue());
		else if (x instanceof Blob)
			setBlob(parameterIndex, (Blob) x);
		else if (x instanceof Clob)
			setClob(parameterIndex, (Clob) x);
		else if (x instanceof Array)
			setArray(parameterIndex, (Array) x);
		else if (x instanceof PGobject)
			setPGobject(parameterIndex, (PGobject) x);
		else if (x instanceof Character)
            setString(parameterIndex, ((Character)x).toString());
		else {
			// Can't infer a type.
			throw new PSQLException(
					GT
							.tr(
									"Can''t infer the SQL type to use for an instance of {0}. Use setObject() with an explicit Types value to specify the type to use.",
									x.getClass().getName()),
					PSQLState.INVALID_PARAMETER_TYPE);
		}
	}

	/*
	 * Before executing a stored procedure call you must explicitly call
	 * registerOutParameter to register the java.sql.Type of each out parameter.
	 * 
	 * <p>Note: When reading the value of an out parameter, you must use the
	 * getXXX method whose Java type XXX corresponds to the parameter's
	 * registered SQL type.
	 * 
	 * ONLY 1 RETURN PARAMETER if {?= call ..} syntax is used
	 * 
	 * @param parameterIndex the first parameter is 1, the second is 2,...
	 * 
	 * @param sqlType SQL type code defined by java.sql.Types; for parameters of
	 * type Numeric or Decimal use the version of registerOutParameter that
	 * accepts a scale value
	 * 
	 * @exception SQLException if a database-access error occurs.
	 */
	public void registerOutParameter(int parameterIndex, int sqlType,
			boolean setPreparedParameters) throws SQLException {
		checkClosed();
		switch (sqlType) {
		case Types.TINYINT:
			// we don't have a TINYINT type use SMALLINT
			sqlType = Types.SMALLINT;
			break;
		case Types.LONGVARCHAR:
			sqlType = Types.VARCHAR;
			break;
		case Types.DECIMAL:
			sqlType = Types.NUMERIC;
			break;
		case Types.FLOAT:
			// float is the same as double
			sqlType = Types.DOUBLE;
			break;
		case Types.VARBINARY:
		case Types.LONGVARBINARY:
			sqlType = Types.BINARY;
			break;
		default:
			break;
		}
		if (!isFunction)
			throw new PSQLException(
					GT
							.tr("This statement does not declare an OUT parameter.  Use '{' ?= call ... '}' to declare one."),
					PSQLState.STATEMENT_NOT_ALLOWED_IN_FUNCTION_CALL);
		checkIndex(parameterIndex, false);

		if (setPreparedParameters)
			preparedParameters.registerOutParameter(parameterIndex, sqlType);
		// functionReturnType contains the user supplied value to check
		// testReturn contains a modified version to make it easier to
		// check the getXXX methods..
		functionReturnType[parameterIndex - 1] = sqlType;
		testReturn[parameterIndex - 1] = sqlType;

		if (functionReturnType[parameterIndex - 1] == Types.CHAR
				|| functionReturnType[parameterIndex - 1] == Types.LONGVARCHAR)
			testReturn[parameterIndex - 1] = Types.VARCHAR;
		else if (functionReturnType[parameterIndex - 1] == Types.FLOAT)
			testReturn[parameterIndex - 1] = Types.REAL; // changes to
		// streamline later
		// error checking
		returnTypeSet = true;
	}

	/*
	 * You must also specify the scale for numeric/decimal types:
	 * 
	 * <p>Note: When reading the value of an out parameter, you must use the
	 * getXXX method whose Java type XXX corresponds to the parameter's
	 * registered SQL type.
	 * 
	 * @param parameterIndex the first parameter is 1, the second is 2,...
	 * 
	 * @param sqlType use either java.sql.Type.NUMERIC or java.sql.Type.DECIMAL
	 * 
	 * @param scale a value greater than or equal to zero representing the
	 * desired number of digits to the right of the decimal point
	 * 
	 * @exception SQLException if a database-access error occurs.
	 */
	public void registerOutParameter(int parameterIndex, int sqlType,
			int scale, boolean setPreparedParameters) throws SQLException {
		registerOutParameter(parameterIndex, sqlType, setPreparedParameters); // ignore
		// for
		// now..
	}

	/*
	 * An OUT parameter may have the value of SQL NULL; wasNull reports whether
	 * the last value read has this special value.
	 * 
	 * <p>Note: You must first call getXXX on a parameter to read its value and
	 * then call wasNull() to see if the value was SQL NULL.
	 * 
	 * @return true if the last parameter read was SQL NULL
	 * 
	 * @exception SQLException if a database-access error occurs.
	 */
	public boolean wasNull() throws SQLException {
		if (lastIndex == 0)
			throw new PSQLException(GT
					.tr("wasNull cannot be call before fetching a result."),
					PSQLState.OBJECT_NOT_IN_STATE);

		// check to see if the last access threw an exception
		return (callResult[lastIndex - 1] == null);
	}

	/*
	 * Get the value of a CHAR, VARCHAR, or LONGVARCHAR parameter as a Java
	 * String.
	 * 
	 * @param parameterIndex the first parameter is 1, the second is 2,...
	 * 
	 * @return the parameter value; if the value is SQL NULL, the result is null
	 * 
	 * @exception SQLException if a database-access error occurs.
	 */
	public String getString(int parameterIndex) throws SQLException {
		checkClosed();
		checkIndex(parameterIndex, Types.VARCHAR, "String");
		return (String) callResult[parameterIndex - 1];
	}

	/*
	 * Get the value of a BIT parameter as a Java boolean.
	 * 
	 * @param parameterIndex the first parameter is 1, the second is 2,...
	 * 
	 * @return the parameter value; if the value is SQL NULL, the result is
	 * false
	 * 
	 * @exception SQLException if a database-access error occurs.
	 */
	public boolean getBoolean(int parameterIndex) throws SQLException {
		checkClosed();
		checkIndex(parameterIndex, Types.BIT, "Boolean");
		if (callResult[parameterIndex - 1] == null)
			return false;

		return ((Boolean) callResult[parameterIndex - 1]).booleanValue();
	}

	/*
	 * Get the value of a TINYINT parameter as a Java byte.
	 * 
	 * @param parameterIndex the first parameter is 1, the second is 2,...
	 * 
	 * @return the parameter value; if the value is SQL NULL, the result is 0
	 * 
	 * @exception SQLException if a database-access error occurs.
	 */
	public byte getByte(int parameterIndex) throws SQLException {
		checkClosed();
		// fake tiny int with smallint
		checkIndex(parameterIndex, Types.SMALLINT, "Byte");

		if (callResult[parameterIndex - 1] == null)
			return 0;

		return ((Integer) callResult[parameterIndex - 1]).byteValue();

	}

	/*
	 * Get the value of a SMALLINT parameter as a Java short.
	 * 
	 * @param parameterIndex the first parameter is 1, the second is 2,...
	 * 
	 * @return the parameter value; if the value is SQL NULL, the result is 0
	 * 
	 * @exception SQLException if a database-access error occurs.
	 */
	public short getShort(int parameterIndex) throws SQLException {
		checkClosed();
		checkIndex(parameterIndex, Types.SMALLINT, "Short");
		if (callResult[parameterIndex - 1] == null)
			return 0;
		return ((Integer) callResult[parameterIndex - 1]).shortValue();
	}

	/*
	 * Get the value of an INTEGER parameter as a Java int.
	 * 
	 * @param parameterIndex the first parameter is 1, the second is 2,...
	 * 
	 * @return the parameter value; if the value is SQL NULL, the result is 0
	 * 
	 * @exception SQLException if a database-access error occurs.
	 */
	public int getInt(int parameterIndex) throws SQLException {
		checkClosed();
		checkIndex(parameterIndex, Types.INTEGER, "Int");
		if (callResult[parameterIndex - 1] == null)
			return 0;

		return ((Integer) callResult[parameterIndex - 1]).intValue();
	}

	/*
	 * Get the value of a BIGINT parameter as a Java long.
	 * 
	 * @param parameterIndex the first parameter is 1, the second is 2,...
	 * 
	 * @return the parameter value; if the value is SQL NULL, the result is 0
	 * 
	 * @exception SQLException if a database-access error occurs.
	 */
	public long getLong(int parameterIndex) throws SQLException {
		checkClosed();
		checkIndex(parameterIndex, Types.BIGINT, "Long");
		if (callResult[parameterIndex - 1] == null)
			return 0;

		return ((Long) callResult[parameterIndex - 1]).longValue();
	}

	/*
	 * Get the value of a FLOAT parameter as a Java float.
	 * 
	 * @param parameterIndex the first parameter is 1, the second is 2,...
	 * 
	 * @return the parameter value; if the value is SQL NULL, the result is 0
	 * 
	 * @exception SQLException if a database-access error occurs.
	 */
	public float getFloat(int parameterIndex) throws SQLException {
		checkClosed();
		checkIndex(parameterIndex, Types.REAL, "Float");
		if (callResult[parameterIndex - 1] == null)
			return 0;

		return ((Float) callResult[parameterIndex - 1]).floatValue();
	}

	/*
	 * Get the value of a DOUBLE parameter as a Java double.
	 * 
	 * @param parameterIndex the first parameter is 1, the second is 2,...
	 * 
	 * @return the parameter value; if the value is SQL NULL, the result is 0
	 * 
	 * @exception SQLException if a database-access error occurs.
	 */
	public double getDouble(int parameterIndex) throws SQLException {
		checkClosed();
		checkIndex(parameterIndex, Types.DOUBLE, "Double");
		if (callResult[parameterIndex - 1] == null)
			return 0;

		return ((Double) callResult[parameterIndex - 1]).doubleValue();
	}

	/*
	 * Get the value of a NUMERIC parameter as a java.math.BigDecimal object.
	 * 
	 * @param parameterIndex the first parameter is 1, the second is 2,...
	 * 
	 * @param scale a value greater than or equal to zero representing the
	 * desired number of digits to the right of the decimal point
	 * 
	 * @return the parameter value; if the value is SQL NULL, the result is null
	 * 
	 * @exception SQLException if a database-access error occurs.
	 * 
	 * @deprecated in Java2.0
	 */
	public BigDecimal getBigDecimal(int parameterIndex, int scale)
			throws SQLException {
		checkClosed();
		checkIndex(parameterIndex, Types.NUMERIC, "BigDecimal");
		return ((BigDecimal) callResult[parameterIndex - 1]);
	}

	/*
	 * Get the value of a SQL BINARY or VARBINARY parameter as a Java byte[]
	 * 
	 * @param parameterIndex the first parameter is 1, the second is 2,...
	 * 
	 * @return the parameter value; if the value is SQL NULL, the result is null
	 * 
	 * @exception SQLException if a database-access error occurs.
	 */
	public byte[] getBytes(int parameterIndex) throws SQLException {
		checkClosed();
		checkIndex(parameterIndex, Types.VARBINARY, Types.BINARY, "Bytes");
		return ((byte[]) callResult[parameterIndex - 1]);
	}

	//这三个getDate、getTime、getTimestamp与另外有三个参数的对应方法不同，
	//后者再转换一次String
	/*
	 * Get the value of a SQL DATE parameter as a java.sql.Date object
	 * 
	 * @param parameterIndex the first parameter is 1, the second is 2,...
	 * 
	 * @return the parameter value; if the value is SQL NULL, the result is null
	 * 
	 * @exception SQLException if a database-access error occurs.
	 */
	public java.sql.Date getDate(int parameterIndex) throws SQLException {
		checkClosed();
		checkIndex(parameterIndex, Types.DATE, "Date");
		return (java.sql.Date) callResult[parameterIndex - 1];
	}

	/*
	 * Get the value of a SQL TIME parameter as a java.sql.Time object.
	 * 
	 * @param parameterIndex the first parameter is 1, the second is 2,...
	 * 
	 * @return the parameter value; if the value is SQL NULL, the result is null
	 * 
	 * @exception SQLException if a database-access error occurs.
	 */
	public java.sql.Time getTime(int parameterIndex) throws SQLException {
		checkClosed();
		checkIndex(parameterIndex, Types.TIME, "Time");
		return (java.sql.Time) callResult[parameterIndex - 1];
	}

	/*
	 * Get the value of a SQL TIMESTAMP parameter as a java.sql.Timestamp
	 * object.
	 * 
	 * @param parameterIndex the first parameter is 1, the second is 2,...
	 * 
	 * @return the parameter value; if the value is SQL NULL, the result is null
	 * 
	 * @exception SQLException if a database-access error occurs.
	 */
	public java.sql.Timestamp getTimestamp(int parameterIndex)
			throws SQLException {
		checkClosed();
		checkIndex(parameterIndex, Types.TIMESTAMP, "Timestamp");
		return (java.sql.Timestamp) callResult[parameterIndex - 1];
	}

	// getObject returns a Java object for the parameter.
	// See the JDBC spec's "Dynamic Programming" chapter for details.
	/*
	 * Get the value of a parameter as a Java object.
	 * 
	 * <p>This method returns a Java object whose type coresponds to the SQL
	 * type that was registered for this parameter using registerOutParameter.
	 * 
	 * <P>Note that this method may be used to read datatabase-specific,
	 * abstract data types. This is done by specifying a targetSqlType of
	 * java.sql.types.OTHER, which allows the driver to return a
	 * database-specific Java type.
	 * 
	 * <p>See the JDBC spec's "Dynamic Programming" chapter for details.
	 * 
	 * @param parameterIndex the first parameter is 1, the second is 2,...
	 * 
	 * @return A java.lang.Object holding the OUT parameter value.
	 * 
	 * @exception SQLException if a database-access error occurs.
	 */
	public Object getObject(int parameterIndex) throws SQLException {
		checkClosed();
		checkIndex(parameterIndex);
		return callResult[parameterIndex - 1];
	}

	/*
	 * Returns the SQL statement with the current template values substituted.
	 */
	public String toString() {
		if (preparedQuery == null)
			return super.toString();

		return preparedQuery.toString(preparedParameters);
	}

	/*
	 * Note if s is a String it should be escaped by the caller to avoid SQL
	 * injection attacks. It is not done here for efficency reasons as most
	 * calls to this method do not require escaping as the source of the string
	 * is known safe (i.e. Integer.toString())
	 */
	protected void bindLiteral(int paramIndex, String s, int oid)
			throws SQLException {
		if (adjustIndex)
			paramIndex--;
		preparedParameters.setLiteralParameter(paramIndex, s, oid);
	}

	/*
	 * This version is for values that should turn into strings e.g. setString
	 * directly calls bindString with no escaping; the per-protocol
	 * ParameterList does escaping as needed.
	 */
	private void bindString(int paramIndex, String s, int oid)
			throws SQLException {
		if (adjustIndex)
			paramIndex--;
		preparedParameters.setStringParameter(paramIndex, s, oid);
	}

	/**
	 * this method will turn a string of the form { [? =] call <some_function>
	 * [(?, [?,..])] } into the PostgreSQL format which is select
	 * <some_function> (?, [?, ...]) as result or select * from <some_function>
	 * (?, [?, ...]) as result (7.3)
	 */
	//如:   "{ ? = call upper( ? ) }"
	//变成: select * from upper(?, ? )  as result
	private String modifyJdbcCall(String p_sql) throws SQLException {
		checkClosed();

		// Mini-parser for JDBC function-call syntax (only)
		// TODO: Merge with escape processing (and parameter parsing?)
		// so we only parse each query once.

		isFunction = false;

		boolean stdStrings = connection.getStandardConformingStrings();

		int len = p_sql.length();
		int state = 1;
		boolean inQuotes = false, inEscape = false;
		outParmBeforeFunc = false;
		int startIndex = -1, endIndex = -1;
		boolean syntaxError = false;
		int i = 0;

		while (i < len && !syntaxError) {
			char ch = p_sql.charAt(i);

			switch (state) {
			case 1: // Looking for { at start of query
				if (ch == '{') {
					++i;
					++state;
				} else if (Character.isWhitespace(ch)) {
					++i;
				} else {
					// Not function-call syntax. Skip the rest of the string.
					i = len;
				}
				break;

			case 2: // After {, looking for ? or =, skipping whitespace
				if (ch == '?') {
					outParmBeforeFunc = isFunction = true; // { ? = call ... }
					// -- function with
					// one out parameter
					++i;
					++state;
				} else if (ch == 'c') { // { call ... } -- proc with no out
					// parameters
					state += 3; // Don't increase 'i'
				} else if (Character.isWhitespace(ch)) {
					++i;
				} else {
					// "{ foo ...", doesn't make sense, complain.
					syntaxError = true;
				}
				break;

			case 3: // Looking for = after ?, skipping whitespace
				if (ch == '=') {
					++i;
					++state;
				} else if (Character.isWhitespace(ch)) {
					++i;
				} else {
					syntaxError = true;
				}
				break;

			case 4: // Looking for 'call' after '? =' skipping whitespace
				if (ch == 'c' || ch == 'C') {
					++state; // Don't increase 'i'.
				} else if (Character.isWhitespace(ch)) {
					++i;
				} else {
					syntaxError = true;
				}
				break;

			case 5: // Should be at 'call ' either at start of string or after
				// ?=
				if ((ch == 'c' || ch == 'C') && i + 4 <= len
						&& p_sql.substring(i, i + 4).equalsIgnoreCase("call")) {
					isFunction = true;
					i += 4;
					++state;
				} else if (Character.isWhitespace(ch)) {
					++i;
				} else {
					syntaxError = true;
				}
				break;

			case 6: // Looking for whitespace char after 'call'
				if (Character.isWhitespace(ch)) {
					// Ok, we found the start of the real call.
					++i;
					++state;
					startIndex = i;
				} else {
					syntaxError = true;
				}
				break;

			case 7: // In "body" of the query (after "{ [? =] call ")
				if (ch == '\'') {
					inQuotes = !inQuotes;
					++i;
				} else if (inQuotes && ch == '\\' && !stdStrings) {
					// Backslash in string constant, skip next character.
					i += 2;
				} else if (!inQuotes && ch == '{') {
					inEscape = !inEscape;
					++i;
				} else if (!inQuotes && ch == '}') {
					if (!inEscape) {
						// Should be end of string.
						endIndex = i;
						++i;
						++state;
					} else {
						inEscape = false;
					}
				} else if (!inQuotes && ch == ';') {
					syntaxError = true;
				} else {
					// Everything else is ok.
					++i;
				}
				break;

			case 8: // At trailing end of query, eating whitespace
				if (Character.isWhitespace(ch)) {
					++i;
				} else {
					syntaxError = true;
				}
				break;

			default:
				throw new IllegalStateException("somehow got into bad state "
						+ state);
			}
		}

		// We can only legally end in a couple of states here.
		if (i == len && !syntaxError) {
			if (state == 1)
				return p_sql; // Not an escaped syntax.
			if (state != 8)
				syntaxError = true; // Ran out of query while still parsing
		}

		if (syntaxError)
			throw new PSQLException(
					GT
							.tr(
									"Malformed function or procedure escape syntax at offset {0}.",
									new Integer(i)),
					PSQLState.STATEMENT_NOT_ALLOWED_IN_FUNCTION_CALL);

		if (connection.haveMinimumServerVersion("8.1")
				&& ((AbstractJdbc2Connection) connection).getProtocolVersion() == 3) {
			String s = p_sql.substring(startIndex, endIndex);
			StringBuffer sb = new StringBuffer(s);
			if (outParmBeforeFunc) {
				// move the single out parameter into the function call
				// so that it can be treated like all other parameters
				boolean needComma = false;

				// have to use String.indexOf for java 2
				int opening = s.indexOf('(') + 1;
				int closing = s.indexOf(')');
				for (int j = opening; j < closing; j++) {
					if (!Character.isWhitespace(sb.charAt(j))) {
						needComma = true;
						break;
					}
				}
				if (needComma) {
					sb.insert(opening, "?,");
				} else {
					sb.insert(opening, "?");
				}

			}
			return "select * from " + sb.toString() + " as result";
		} else {
			return "select " + p_sql.substring(startIndex, endIndex)
					+ " as result";
		}
	}

	/**
	 * helperfunction for the getXXX calls to check isFunction and index == 1
	 * Compare BOTH type fields against the return type.
	 */
	protected void checkIndex(int parameterIndex, int type1, int type2,
			String getName) throws SQLException {
		checkIndex(parameterIndex);
		if (type1 != this.testReturn[parameterIndex - 1]
				&& type2 != this.testReturn[parameterIndex - 1])
			throw new PSQLException(
					GT
							.tr(
									"Parameter of type {0} was registered, but call to get{1} (sqltype={2}) was made.",
									new Object[] {
											"java.sql.Types="
													+ testReturn[parameterIndex - 1],
											getName, "java.sql.Types=" + type1 }),
					PSQLState.MOST_SPECIFIC_TYPE_DOES_NOT_MATCH);
	}

	/**
	 * helperfunction for the getXXX calls to check isFunction and index == 1
	 */
	protected void checkIndex(int parameterIndex, int type, String getName)
			throws SQLException {
		checkIndex(parameterIndex);
		if (type != this.testReturn[parameterIndex - 1])
			throw new PSQLException(
					GT
							.tr(
									"Parameter of type {0} was registered, but call to get{1} (sqltype={2}) was made.",
									new Object[] {
											"java.sql.Types="
													+ testReturn[parameterIndex - 1],
											getName, "java.sql.Types=" + type }),
					PSQLState.MOST_SPECIFIC_TYPE_DOES_NOT_MATCH);
	}

	private void checkIndex(int parameterIndex) throws SQLException {
		checkIndex(parameterIndex, true);
	}

	/**
	 * helperfunction for the getXXX calls to check isFunction and index == 1
	 * 
	 * @param parameterIndex
	 *            index of getXXX (index) check to make sure is a function and
	 *            index == 1
	 */
	private void checkIndex(int parameterIndex, boolean fetchingData)
			throws SQLException {
		if (!isFunction)
			throw new PSQLException(
					GT
							.tr("A CallableStatement was declared, but no call to registerOutParameter(1, <some type>) was made."),
					PSQLState.STATEMENT_NOT_ALLOWED_IN_FUNCTION_CALL);

		if (fetchingData) {
			if (!returnTypeSet)
				throw new PSQLException(GT
						.tr("No function outputs were registered."),
						PSQLState.OBJECT_NOT_IN_STATE);

			if (callResult == null)
				throw new PSQLException(
						GT
								.tr("Results cannot be retrieved from a CallableStatement before it is executed."),
						PSQLState.NO_DATA);

			lastIndex = parameterIndex;
		}
	}

	public void setPrepareThreshold(int newThreshold) throws SQLException {
		checkClosed();

		if (newThreshold < 0)
			newThreshold = 0;

		this.m_prepareThreshold = newThreshold;
	}

	public int getPrepareThreshold() {
		return m_prepareThreshold;
	}

	public void setUseServerPrepare(boolean flag) throws SQLException {
		setPrepareThreshold(flag ? 1 : 0);
	}

	public boolean isUseServerPrepare() {
		return (preparedQuery != null && m_prepareThreshold != 0 && m_useCount + 1 >= m_prepareThreshold);
	}

	protected void checkClosed() throws SQLException {
		if (isClosed)
			throw new PSQLException(GT.tr("This statement has been closed."),
					PSQLState.OBJECT_NOT_IN_STATE);
	}

	// ** JDBC 2 Extensions **

	public void addBatch(String p_sql) throws SQLException {
		checkClosed();

		//相当于addBatch()和addBatch(sql)是不能同时用的，
		//比如:
		/*
		ps = conn.prepareStatement("insert into pet(name,age) values(?,?)");
		ps.setString(1, "name1");
		ps.setInt(2, 1000);
		ps.addBatch();
		ps.addBatch();
		ps.addBatch();
		ps.addBatch();
		ps.addBatch("delete from pet where age=1000");

		int[] updates = ps.executeBatch();
		*/
		if (preparedQuery != null)
			throw new PSQLException(
					GT
							.tr("Can''t use query methods that take a query string on a PreparedStatement."),
					PSQLState.WRONG_OBJECT_TYPE);

		if (batchStatements == null) {
			batchStatements = new ArrayList();
			batchParameters = new ArrayList();
		}

		p_sql = replaceProcessing(p_sql);

		batchStatements.add(connection.getQueryExecutor().createSimpleQuery(
				p_sql));
		batchParameters.add(null);
	}

	public void clearBatch() throws SQLException {
		if (batchStatements != null) {
			batchStatements.clear();
			batchParameters.clear();
		}
	}

	//
	// ResultHandler for batch queries.
	//

	private class BatchResultHandler implements ResultHandler {
		private BatchUpdateException batchException = null;
		private int resultIndex = 0;

		private final Query[] queries;
		private final ParameterList[] parameterLists;
		private final int[] updateCounts;

		BatchResultHandler(Query[] queries, ParameterList[] parameterLists,
				int[] updateCounts) {
			this.queries = queries;
			this.parameterLists = parameterLists;
			this.updateCounts = updateCounts;
		}

		public void handleResultRows(Query fromQuery, Field[] fields,
				Vector tuples, ResultCursor cursor) {
			handleError(new PSQLException(GT
					.tr("A result was returned when none was expected."),
					PSQLState.TOO_MANY_RESULTS));
		}

		public void handleCommandStatus(String status, int updateCount,
				long insertOID) {
			if (resultIndex >= updateCounts.length) {
				handleError(new PSQLException(GT
						.tr("Too many update results were returned."),
						PSQLState.TOO_MANY_RESULTS));
				return;
			}

			updateCounts[resultIndex++] = updateCount;
		}

		public void handleWarning(SQLWarning warning) {
			AbstractJdbc2Statement.this.addWarning(warning);
		}

		public void handleError(SQLException newError) {
			if (batchException == null) {
				int[] successCounts;

				if (resultIndex >= updateCounts.length)
					successCounts = updateCounts;
				else {
					successCounts = new int[resultIndex];
					System.arraycopy(updateCounts, 0, successCounts, 0,
							resultIndex);
				}

				String queryString = "<unknown>";
				if (resultIndex < queries.length)
					queryString = queries[resultIndex]
							.toString(parameterLists[resultIndex]);

				batchException = new BatchUpdateException(
						GT
								.tr(
										"Batch entry {0} {1} was aborted.  Call getNextException to see the cause.",
										new Object[] {
												new Integer(resultIndex),
												queryString }), newError
								.getSQLState(), successCounts);
			}

			batchException.setNextException(newError);
		}

		public void handleCompletion() throws SQLException {
			if (batchException != null)
				throw batchException;
		}
	}

	//除了handleResultRows不一样之外，其他与BatchResultHandler相同
	private class CallableBatchResultHandler implements ResultHandler {
		private BatchUpdateException batchException = null;
		private int resultIndex = 0;

		private final Query[] queries;
		private final ParameterList[] parameterLists;
		private final int[] updateCounts;

		CallableBatchResultHandler(Query[] queries,
				ParameterList[] parameterLists, int[] updateCounts) {
			this.queries = queries;
			this.parameterLists = parameterLists;
			this.updateCounts = updateCounts;
		}

		public void handleResultRows(Query fromQuery, Field[] fields,
				Vector tuples, ResultCursor cursor) {

		}

		public void handleCommandStatus(String status, int updateCount,
				long insertOID) {
			if (resultIndex >= updateCounts.length) {
				handleError(new PSQLException(GT
						.tr("Too many update results were returned."),
						PSQLState.TOO_MANY_RESULTS));
				return;
			}

			updateCounts[resultIndex++] = updateCount;
		}

		public void handleWarning(SQLWarning warning) {
			AbstractJdbc2Statement.this.addWarning(warning);
		}

		public void handleError(SQLException newError) {
			if (batchException == null) {
				int[] successCounts;

				if (resultIndex >= updateCounts.length)
					successCounts = updateCounts;
				else {
					successCounts = new int[resultIndex];
					System.arraycopy(updateCounts, 0, successCounts, 0,
							resultIndex);
				}

				String queryString = "<unknown>";
				if (resultIndex < queries.length)
					queryString = queries[resultIndex]
							.toString(parameterLists[resultIndex]);

				batchException = new BatchUpdateException(
						GT
								.tr(
										"Batch entry {0} {1} was aborted.  Call getNextException to see the cause.",
										new Object[] {
												new Integer(resultIndex),
												queryString }), newError
								.getSQLState(), successCounts);
			}

			batchException.setNextException(newError);
		}

		public void handleCompletion() throws SQLException {
			if (batchException != null)
				throw batchException;
		}
	}

	//当addBatch(sql)时，实际上是逐条sql一一执行的，并且不能有select语句
	//所有batchStatements、batchParameters参数也会清除
	public int[] executeBatch() throws SQLException {
		try {//我加上的
		DEBUG.P(this,"executeBatch()");

		checkClosed();

		// Every statement execution clears any previous warnings.
		clearWarnings();

		//batchStatements和batchParameters的size是保持一致的
		//当addBatch(sql)时，batchParameters加null;
		//当addBatch()时，batchStatements重复加同一条sql对应的Query实例

		if (batchStatements == null || batchStatements.isEmpty())
			return new int[0];

		int size = batchStatements.size();
		int[] updateCounts = new int[size];

		// Construct query/parameter arrays.
		Query[] queries = (Query[]) batchStatements
				.toArray(new Query[batchStatements.size()]);
		ParameterList[] parameterLists = (ParameterList[]) batchParameters
				.toArray(new ParameterList[batchParameters.size()]);
		batchStatements.clear();
		batchParameters.clear();

		DEBUG.P("firstUnclosedResult="+firstUnclosedResult);

		// Close any existing resultsets associated with this statement.
		while (firstUnclosedResult != null) {
			if (firstUnclosedResult.getResultSet() != null) {
				firstUnclosedResult.getResultSet().close();
			}
			firstUnclosedResult = firstUnclosedResult.getNext();
		}

		DEBUG.P("lastSimpleQuery="+lastSimpleQuery);

		if (lastSimpleQuery != null) {
			lastSimpleQuery.close();
			lastSimpleQuery = null;
		}

		int flags = QueryExecutor.QUERY_NO_RESULTS;

		DEBUG.P("preparedQuery="+preparedQuery);

		// Only use named statements after we hit the threshold
		if (preparedQuery != null) {
			m_useCount += queries.length;
		}

		DEBUG.P("m_prepareThreshold="+m_prepareThreshold);
		DEBUG.P("m_useCount="+m_useCount);

		if (m_prepareThreshold == 0 || m_useCount < m_prepareThreshold)
			flags |= QueryExecutor.QUERY_ONESHOT;

		if (connection.getAutoCommit())
			flags |= QueryExecutor.QUERY_SUPPRESS_BEGIN;

		result = null;

		DEBUG.P("isFunction="+isFunction);

		ResultHandler handler;
		if (isFunction) {
			handler = new CallableBatchResultHandler(queries, parameterLists,
					updateCounts);
		} else {
			handler = new BatchResultHandler(queries, parameterLists,
					updateCounts);
		}

		connection.getQueryExecutor().execute(queries, parameterLists, handler,
				maxrows, fetchSize, flags);

		return updateCounts;

		}finally{//我加上的
		DEBUG.P(0,this,"executeBatch()");
		}
	}

	/*
	 * Cancel can be used by one thread to cancel a statement that is being
	 * executed by another thread. <p>
	 * 
	 * @exception SQLException only because thats the spec.
	 */
	public void cancel() throws SQLException {
		connection.cancelQuery();
	}

	public Connection getConnection() throws SQLException {
		return (Connection) connection;
	}

	public int getFetchDirection() {
		return fetchdirection;
	}

	public int getResultSetConcurrency() {
		return concurrency;
	}

	public int getResultSetType() {
		return resultsettype;
	}

	public void setFetchDirection(int direction) throws SQLException {
		switch (direction) {
		case ResultSet.FETCH_FORWARD:
		case ResultSet.FETCH_REVERSE:
		case ResultSet.FETCH_UNKNOWN:
			fetchdirection = direction;
			break;
		default:
			throw new PSQLException(GT.tr(
					"Invalid fetch direction constant: {0}.", new Integer(
							direction)), PSQLState.INVALID_PARAMETER_VALUE);
		}
	}

	public void setFetchSize(int rows) throws SQLException {
		checkClosed();
		if (rows < 0)
			throw new PSQLException(
					GT
							.tr("Fetch size must be a value greater to or equal to 0."),
					PSQLState.INVALID_PARAMETER_VALUE);
		fetchSize = rows;
	}

	public void addBatch() throws SQLException {
		checkClosed();

		if (batchStatements == null) {
			batchStatements = new ArrayList();
			batchParameters = new ArrayList();
		}

		// we need to create copies of our parameters, otherwise the values can
		// be changed
		batchStatements.add(preparedQuery);
		batchParameters.add(preparedParameters.copy());
	}

	public ResultSetMetaData getMetaData() throws SQLException {
		checkClosed();
		ResultSet rs = getResultSet();

		if (rs == null) {
			// OK, we haven't executed it yet, we've got to go to the backend
			// for more info. We send the full query, but just don't
			// execute it.

			int flags = QueryExecutor.QUERY_ONESHOT
					| QueryExecutor.QUERY_DESCRIBE_ONLY
					| QueryExecutor.QUERY_SUPPRESS_BEGIN;
			StatementResultHandler handler = new StatementResultHandler();
			connection.getQueryExecutor().execute(preparedQuery,
					preparedParameters, handler, 0, 0, flags);
			ResultWrapper wrapper = handler.getResults();
			if (wrapper != null) {
				rs = wrapper.getResultSet();
			}
		}

		if (rs != null)
			return rs.getMetaData();

		return null;
	}

	public void setArray(int i, java.sql.Array x) throws SQLException {
		checkClosed();

		if (null == x) {
			setNull(i, Types.ARRAY);
			return;
		}

		// This only works for Array implementations that return a valid array
		// literal from Array.toString(), such as the implementation we return
		// from ResultSet.getArray(). Eventually we need a proper implementation
		// here that works for any Array implementation.

		// Use a typename that is "_" plus the base type; this matches how the
		// backend looks for array types.
		String typename = "_" + x.getBaseTypeName();
		int oid = connection.getTypeInfo().getPGType(typename);
		if (oid == Oid.UNSPECIFIED)
			throw new PSQLException(GT.tr("Unknown type {0}.", typename),
					PSQLState.INVALID_PARAMETER_TYPE);

		setString(i, x.toString(), oid);
	}

	public void setBlob(int i, Blob x) throws SQLException {
		checkClosed();

		if (x == null) {
			setNull(i, Types.BLOB);
			return;
		}

		InputStream l_inStream = x.getBinaryStream();
		LargeObjectManager lom = connection.getLargeObjectAPI();
		long oid = lom.createLO();
		LargeObject lob = lom.open(oid);
		OutputStream los = lob.getOutputStream();
		byte[] buf = new byte[4096];
		try {
			// could be buffered, but then the OutputStream returned by
			// LargeObject
			// is buffered internally anyhow, so there would be no performance
			// boost gained, if anything it would be worse!
			int bytesRemaining = (int) x.length();
			int numRead = l_inStream.read(buf, 0, Math.min(buf.length,
					bytesRemaining));
			while (numRead != -1 && bytesRemaining > 0) {
				bytesRemaining -= numRead;
				if (numRead == buf.length)
					los.write(buf); // saves a buffer creation and copy in
				// LargeObject since it's full
				else
					los.write(buf, 0, numRead);
				numRead = l_inStream.read(buf, 0, Math.min(buf.length,
						bytesRemaining));
			}
		} catch (IOException se) {
			throw new PSQLException(GT
					.tr("Unexpected error writing large object to database."),
					PSQLState.UNEXPECTED_ERROR, se);
		} finally {
			try {
				los.close();
				l_inStream.close();
			} catch (Exception e) {
			}
		}
		setLong(i, oid);
	}

	public void setCharacterStream(int i, java.io.Reader x, int length)
			throws SQLException {
		checkClosed();

		if (x == null) {
			if (connection.haveMinimumServerVersion("7.2")) {
				setNull(i, Types.VARCHAR);
			} else {
				setNull(i, Types.CLOB);
			}
			return;
		}

		if (length < 0)
			throw new PSQLException(GT.tr("Invalid stream length {0}.",
					new Integer(length)), PSQLState.INVALID_PARAMETER_VALUE);

		if (connection.haveMinimumCompatibleVersion("7.2")) {
			// Version 7.2 supports CharacterStream for for the PG text types
			// As the spec/javadoc for this method indicate this is to be used
			// for
			// large text values (i.e. LONGVARCHAR) PG doesn't have a separate
			// long varchar datatype, but with toast all the text datatypes are
			// capable of
			// handling very large values. Thus the implementation ends up
			// calling
			// setString() since there is no current way to stream the value to
			// the server
			char[] l_chars = new char[length];
			int l_charsRead = 0;
			try {
				while (true) {
					int n = x.read(l_chars, l_charsRead, length - l_charsRead);
					if (n == -1)
						break;

					l_charsRead += n;

					if (l_charsRead == length)
						break;
				}
			} catch (IOException l_ioe) {
				throw new PSQLException(GT.tr("Provided Reader failed."),
						PSQLState.UNEXPECTED_ERROR, l_ioe);
			}
			setString(i, new String(l_chars, 0, l_charsRead));
		} else {
			// Version 7.1 only supported streams for LargeObjects
			// but the jdbc spec indicates that streams should be
			// available for LONGVARCHAR instead
			LargeObjectManager lom = connection.getLargeObjectAPI();
			long oid = lom.createLO();
			LargeObject lob = lom.open(oid);
			OutputStream los = lob.getOutputStream();
			try {
				// could be buffered, but then the OutputStream returned by
				// LargeObject
				// is buffered internally anyhow, so there would be no
				// performance
				// boost gained, if anything it would be worse!
				int c = x.read();
				int p = 0;
				while (c > -1 && p < length) {
					los.write(c);
					c = x.read();
					p++;
				}
				los.close();
			} catch (IOException se) {
				throw new PSQLException(
						GT
								.tr("Unexpected error writing large object to database."),
						PSQLState.UNEXPECTED_ERROR, se);
			}
			// lob is closed by the stream so don't call lob.close()
			setLong(i, oid);
		}
	}

	public void setClob(int i, Clob x) throws SQLException {
		checkClosed();

		if (x == null) {
			setNull(i, Types.CLOB);
			return;
		}

		InputStream l_inStream = x.getAsciiStream();
		int l_length = (int) x.length();
		LargeObjectManager lom = connection.getLargeObjectAPI();
		long oid = lom.createLO();
		LargeObject lob = lom.open(oid);
		OutputStream los = lob.getOutputStream();
		try {
			// could be buffered, but then the OutputStream returned by
			// LargeObject
			// is buffered internally anyhow, so there would be no performance
			// boost gained, if anything it would be worse!
			int c = l_inStream.read();
			int p = 0;
			while (c > -1 && p < l_length) {
				los.write(c);
				c = l_inStream.read();
				p++;
			}
			los.close();
		} catch (IOException se) {
			throw new PSQLException(GT
					.tr("Unexpected error writing large object to database."),
					PSQLState.UNEXPECTED_ERROR, se);
		}
		// lob is closed by the stream so don't call lob.close()
		setLong(i, oid);
	}

	public void setNull(int i, int t, String s) throws SQLException {
		checkClosed();
		setNull(i, t);
	}

	public void setRef(int i, Ref x) throws SQLException {
		throw Driver.notImplemented(this.getClass(), "setRef(int,Ref)");
	}

	public void setDate(int i, java.sql.Date d, java.util.Calendar cal)
			throws SQLException {
		checkClosed();

		if (d == null) {
			setNull(i, Types.DATE);
			return;
		}

		if (cal != null)
			cal = (Calendar) cal.clone();

		// We must use UNSPECIFIED here, or inserting a Date-with-timezone into
		// a
		// timestamptz field does an unexpected rotation by the server's
		// TimeZone:
		//
		// We want to interpret 2005/01/01 with calendar +0100 as
		// "local midnight in +0100", but if we go via date it interprets it
		// as local midnight in the server's timezone:

		// template1=# select '2005-01-01+0100'::timestamptz;
		// timestamptz
		// ------------------------
		// 2005-01-01 02:00:00+03
		// (1 row)

		// template1=# select '2005-01-01+0100'::date::timestamptz;
		// timestamptz
		// ------------------------
		// 2005-01-01 00:00:00+03
		// (1 row)

		bindString(i, connection.getTimestampUtils().toString(cal, d),
				Oid.UNSPECIFIED);
	}

	public void setTime(int i, Time t, java.util.Calendar cal)
			throws SQLException {
		checkClosed();

		if (t == null) {
			setNull(i, Types.TIME);
			return;
		}

		if (cal != null)
			cal = (Calendar) cal.clone();

		bindString(i, connection.getTimestampUtils().toString(cal, t),
				Oid.UNSPECIFIED);
	}

	public void setTimestamp(int i, Timestamp t, java.util.Calendar cal)
			throws SQLException {
		checkClosed();

		if (t == null) {
			setNull(i, Types.TIMESTAMP);
			return;
		}

		if (cal != null)
			cal = (Calendar) cal.clone();

		// Use UNSPECIFIED as a compromise to get both TIMESTAMP and TIMESTAMPTZ
		// working.
		// This is because you get this in a +1300 timezone:
		// 
		// template1=# select '2005-01-01 15:00:00 +1000'::timestamptz;
		// timestamptz
		// ------------------------
		// 2005-01-01 18:00:00+13
		// (1 row)

		// template1=# select '2005-01-01 15:00:00 +1000'::timestamp;
		// timestamp
		// ---------------------
		// 2005-01-01 15:00:00
		// (1 row)

		// template1=# select '2005-01-01 15:00:00
		// +1000'::timestamptz::timestamp;
		// timestamp
		// ---------------------
		// 2005-01-01 18:00:00
		// (1 row)

		// So we want to avoid doing a timestamptz -> timestamp conversion, as
		// that
		// will first convert the timestamptz to an equivalent time in the
		// server's
		// timezone (+1300, above), then turn it into a timestamp with the
		// "wrong"
		// time compared to the string we originally provided. But going
		// straight
		// to timestamp is OK as the input parser for timestamp just throws away
		// the timezone part entirely. Since we don't know ahead of time what
		// type
		// we're actually dealing with, UNSPECIFIED seems the lesser evil, even
		// if it
		// does give more scope for type-mismatch errors being silently hidden.

		bindString(i, connection.getTimestampUtils().toString(cal, t),
				Oid.UNSPECIFIED); // Let the server infer the right type.
	}

	// ** JDBC 2 Extensions for CallableStatement**

	public java.sql.Array getArray(int i) throws SQLException {
		checkClosed();
		checkIndex(i, Types.ARRAY, "Array");
		return (Array) callResult[i - 1];
	}

	public java.math.BigDecimal getBigDecimal(int parameterIndex)
			throws SQLException {
		checkClosed();
		checkIndex(parameterIndex, Types.NUMERIC, "BigDecimal");
		return ((BigDecimal) callResult[parameterIndex - 1]);
	}

	public Blob getBlob(int i) throws SQLException {
		throw Driver.notImplemented(this.getClass(), "getBlob(int)");
	}

	public Clob getClob(int i) throws SQLException {
		throw Driver.notImplemented(this.getClass(), "getClob(int)");
	}

	public Object getObjectImpl(int i, java.util.Map map) throws SQLException {
		if (map == null || map.isEmpty()) {
			return getObject(i);
		}
		throw Driver.notImplemented(this.getClass(), "getObjectImpl(int,Map)");
	}

	public Ref getRef(int i) throws SQLException {
		throw Driver.notImplemented(this.getClass(), "getRef(int)");
	}

	//getDate、getTime、getTimestamp都没有直接(XXX)callResult[i - 1]
	public java.sql.Date getDate(int i, java.util.Calendar cal)
			throws SQLException {
		checkClosed();
		checkIndex(i, Types.DATE, "Date");

		if (callResult[i - 1] == null)
			return null;

		if (cal != null)
			cal = (Calendar) cal.clone();

		String value = callResult[i - 1].toString();
		return connection.getTimestampUtils().toDate(cal, value);
	}

	public Time getTime(int i, java.util.Calendar cal) throws SQLException {
		checkClosed();
		checkIndex(i, Types.TIME, "Time");

		if (callResult[i - 1] == null)
			return null;

		if (cal != null)
			cal = (Calendar) cal.clone();

		String value = callResult[i - 1].toString();
		return connection.getTimestampUtils().toTime(cal, value);
	}

	public Timestamp getTimestamp(int i, java.util.Calendar cal)
			throws SQLException {
		checkClosed();
		checkIndex(i, Types.TIMESTAMP, "Timestamp");

		if (callResult[i - 1] == null)
			return null;

		if (cal != null)
			cal = (Calendar) cal.clone();

		String value = callResult[i - 1].toString();
		return connection.getTimestampUtils().toTimestamp(cal, value);
	}

	// no custom types allowed yet..
	public void registerOutParameter(int parameterIndex, int sqlType,
			String typeName) throws SQLException {
		throw Driver.notImplemented(this.getClass(),
				"registerOutParameter(int,int,String)");
	}

}
